use crate::models::CategoryID;
use crate::schema::categories;
use diesel::backend::Backend;
use diesel::deserialize;
use diesel::deserialize::FromSql;
use diesel::serialize;
use diesel::serialize::{Output, ToSql};
use diesel::sql_types::Integer;
use diesel::sqlite::Sqlite;
use serde_derive::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use std::io::Write;

#[derive(Identifiable, Clone, Insertable, Queryable, PartialEq, Eq, Debug)]
#[primary_key(category_id)]
#[table_name = "categories"]
pub struct Category {
    pub category_id: CategoryID,
    pub label: String,
    pub parent_id: CategoryID,
    pub sort_index: Option<i32>,
    pub category_type: CategoryType,
}

impl Category {
    pub fn decompose(self) -> (CategoryID, String, CategoryID, Option<i32>) {
        (self.category_id, self.label, self.parent_id, self.sort_index)
    }
}

impl Hash for Category {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.category_id.hash(state);
        self.parent_id.hash(state);
    }
}

//------------------------------------------------------------------

#[derive(PartialEq, Eq, Copy, Clone, Debug, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[sql_type = "Integer"]
pub enum CategoryType {
    Default,
    Generated,
}

impl CategoryType {
    pub fn to_int(self) -> i32 {
        self as i32
    }

    pub fn from_int(int: i32) -> Self {
        match int {
            0 => CategoryType::Default,
            1 => CategoryType::Generated,

            _ => CategoryType::Default,
        }
    }
}

impl FromSql<Integer, Sqlite> for CategoryType {
    fn from_sql(bytes: Option<&<Sqlite as Backend>::RawValue>) -> deserialize::Result<Self> {
        let int = not_none!(bytes).read_integer();
        Ok(CategoryType::from_int(int))
    }
}

impl ToSql<Integer, Sqlite> for CategoryType {
    fn to_sql<W: Write>(&self, out: &mut Output<'_, W, Sqlite>) -> serialize::Result {
        ToSql::<Integer, Sqlite>::to_sql(&self.to_int(), out)
    }
}
