use crate::models::error::{ModelError, ModelErrorKind};
use diesel::backend::Backend;
use diesel::deserialize;
use diesel::deserialize::FromSql;
use diesel::serialize;
use diesel::serialize::{Output, ToSql};
use diesel::sql_types::Text;
use diesel::sqlite::Sqlite;
use failure::ResultExt;
use log::error;
use std::fmt;
use std::io::Write;
use std::str;
use url;

#[derive(Debug, PartialEq, Eq, Hash, Clone, AsExpression, FromSqlRow)]
#[sql_type = "Text"]
pub struct Url(url::Url);

impl Url {
    pub fn new(url: url::Url) -> Self {
        Url(url)
    }

    pub fn parse(url: &str) -> Result<Self, ModelError> {
        let url = url::Url::parse(url)
            .map_err(move |err| {
                error!("Failed parsing url: {}", url);
                err
            })
            .context(ModelErrorKind::Url)?;
        Ok(Url::new(url))
    }

    pub fn get(&self) -> url::Url {
        self.0.clone()
    }

    pub fn host(&self) -> Option<&str> {
        self.0.host_str()
    }
}

impl fmt::Display for Url {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl FromSql<Text, Sqlite> for Url {
    fn from_sql(bytes: Option<&<Sqlite as Backend>::RawValue>) -> deserialize::Result<Self> {
        let bytes = not_none!(bytes).read_blob();
        let string = str::from_utf8(bytes as &[u8])?;
        let url = url::Url::parse(string)?;
        Ok(Url::new(url))
    }
}

impl ToSql<Text, Sqlite> for Url {
    fn to_sql<W: Write>(&self, out: &mut Output<'_, W, Sqlite>) -> serialize::Result {
        ToSql::<Text, Sqlite>::to_sql(&self.0.as_str(), out)
    }
}
