use std::convert::TryFrom;
use std::fmt::{Debug, Formatter};
use std::io::Write;
use std::str::FromStr;

use diesel::backend::Backend;
use diesel::deserialize::FromSql;
use diesel::serialize::{IsNull, Output, ToSql};
use diesel::sql_types::Binary;
use diesel::sqlite::Sqlite;
use diesel::{deserialize, serialize};
use serde::{Serialize, Serializer};
use uuid::Uuid;

#[derive(AsExpression, FromSqlRow, PartialEq, Clone)]
#[sql_type = "Binary"]
pub enum EmgauwaUid {
    Off,
    On,
    Any(Uuid),
}

impl EmgauwaUid {
    const OFF_STR: &'static str = "off";
    const ON_STR: &'static str = "on";
    const OFF_U8: u8 = 0;
    const ON_U8: u8 = 1;
    const OFF_U128: u128 = 0;
    const ON_U128: u128 = 1;
}

impl Default for EmgauwaUid {
    fn default() -> Self {
        EmgauwaUid::Any(Uuid::new_v4())
    }
}

impl Debug for EmgauwaUid {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            EmgauwaUid::Off => EmgauwaUid::OFF_STR.fmt(f),
            EmgauwaUid::On => EmgauwaUid::ON_STR.fmt(f),
            EmgauwaUid::Any(value) => value.fmt(f),
        }
    }
}

impl ToSql<Binary, Sqlite> for EmgauwaUid {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Sqlite>) -> serialize::Result {
        match self {
            EmgauwaUid::Off => out.write_all(&[EmgauwaUid::OFF_U8])?,
            EmgauwaUid::On => out.write_all(&[EmgauwaUid::ON_U8])?,
            EmgauwaUid::Any(value) => out.write_all(value.as_bytes())?,
        }
        Ok(IsNull::No)
    }
}

impl FromSql<Binary, Sqlite> for EmgauwaUid {
    fn from_sql(bytes: Option<&<Sqlite as Backend>::RawValue>) -> deserialize::Result<Self> {
        match bytes {
            None => Ok(EmgauwaUid::default()),
            Some(value) => match value.read_blob() {
                [EmgauwaUid::OFF_U8] => Ok(EmgauwaUid::Off),
                [EmgauwaUid::ON_U8] => Ok(EmgauwaUid::On),
                value_bytes => Ok(EmgauwaUid::Any(Uuid::from_slice(value_bytes).unwrap())),
            },
        }
    }
}

impl Serialize for EmgauwaUid {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        String::from(self).serialize(serializer)
    }
}

impl From<Uuid> for EmgauwaUid {
    fn from(uid: Uuid) -> EmgauwaUid {
        match uid.as_u128() {
            EmgauwaUid::OFF_U128 => EmgauwaUid::Off,
            EmgauwaUid::ON_U128 => EmgauwaUid::On,
            _ => EmgauwaUid::Any(uid),
        }
    }
}

impl TryFrom<&str> for EmgauwaUid {
    type Error = uuid::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            EmgauwaUid::OFF_STR => Ok(EmgauwaUid::Off),
            EmgauwaUid::ON_STR => Ok(EmgauwaUid::On),
            any => match Uuid::from_str(any) {
                Ok(uuid) => Ok(EmgauwaUid::Any(uuid)),
                Err(err) => Err(err),
            },
        }
    }
}

impl From<&EmgauwaUid> for Uuid {
    fn from(emgauwa_uid: &EmgauwaUid) -> Uuid {
        match emgauwa_uid {
            EmgauwaUid::Off => uuid::Uuid::from_u128(EmgauwaUid::OFF_U128),
            EmgauwaUid::On => uuid::Uuid::from_u128(EmgauwaUid::ON_U128),
            EmgauwaUid::Any(value) => *value,
        }
    }
}

impl From<&EmgauwaUid> for String {
    fn from(emgauwa_uid: &EmgauwaUid) -> String {
        match emgauwa_uid {
            EmgauwaUid::Off => String::from(EmgauwaUid::OFF_STR),
            EmgauwaUid::On => String::from(EmgauwaUid::ON_STR),
            EmgauwaUid::Any(value) => value.to_hyphenated().to_string(),
        }
    }
}