// Copyright (c) 2017, Marty Mills <daggerbot@gmail.com>
// This software is available under the terms of the zlib license.
// See COPYING.md for more information.

use clamp::{Clamp, ClampInto};
use lum::{Lum, Luma, Luminance};
use rgb::{Rgb, Rgba};

/// Lossy conversion from another color type.
pub trait FromColor<F> {
    fn from_color (color: F) -> Self;
}

/// Lossy conversion into another color type.
pub trait IntoColor<T> {
    fn into_color (self) -> T;
}

impl<F, T> IntoColor<T> for F
    where T: FromColor<F>
{
    #[inline(always)]
    fn into_color (self) -> T { T::from_color(self) }
}

// Conversion to Lum<T>

impl<F, T> FromColor<Lum<F>> for Lum<T>
    where F: ClampInto<T>
{
    fn from_color (color: Lum<F>) -> Lum<T> {
        Lum { l: color.l.clamp_into() }
    }
}

impl<F, T> FromColor<Option<Lum<F>>> for Lum<T>
    where F: ClampInto<T>, T: Clamp
{
    fn from_color (color: Option<Lum<F>>) -> Lum<T> {
        match color {
            None => Lum::clamp_min(),
            Some(color) => Lum::from_color(color),
        }
    }
}

impl<F, T> FromColor<Luma<F>> for Lum<T>
    where F: ClampInto<T>
{
    fn from_color (color: Luma<F>) -> Lum<T> {
        Lum::from_color(color.drop_alpha())
    }
}

impl<F, T> FromColor<Rgb<F>> for Lum<T>
    where Rgb<F>: Luminance, f64: ClampInto<T>
{
    fn from_color (color: Rgb<F>) -> Lum<T> {
        Lum { l: color.luminance().clamp_into() }
    }
}

impl<F, T> FromColor<Option<Rgb<F>>> for Lum<T>
    where Rgb<F>: Luminance, f64: ClampInto<T>, T: Clamp
{
    fn from_color (color: Option<Rgb<F>>) -> Lum<T> {
        match color {
            None => Lum::clamp_min(),
            Some(color) => Lum::from_color(color),
        }
    }
}

impl<F, T> FromColor<Rgba<F>> for Lum<T>
    where Rgb<F>: Luminance, f64: ClampInto<T>
{
    fn from_color (color: Rgba<F>) -> Lum<T> {
        Lum::from_color(color.drop_alpha())
    }
}

// Conversion to Option<Lum<T>>

impl<F, T> FromColor<Lum<F>> for Option<Lum<T>>
    where F: ClampInto<T>
{
    fn from_color (color: Lum<F>) -> Option<Lum<T>> {
        Some(Lum::from_color(color))
    }
}

impl<F, T> FromColor<Option<Lum<F>>> for Option<Lum<T>>
    where F: ClampInto<T>
{
    fn from_color (color: Option<Lum<F>>) -> Option<Lum<T>> {
        match color {
            None => None,
            Some(color) => Some(Lum::from_color(color)),
        }
    }
}

impl<F, T> FromColor<Luma<F>> for Option<Lum<T>>
    where F: PartialOrd + Clamp + ClampInto<T>
{
    fn from_color (color: Luma<F>) -> Option<Lum<T>> {
        if color.a > F::clamp_min() {
            Some(Lum::from_color(color.drop_alpha()))
        } else {
            None
        }
    }
}

impl<F, T> FromColor<Rgb<F>> for Option<Lum<T>>
    where Rgb<F>: Luminance, f64: ClampInto<T>
{
    fn from_color (color: Rgb<F>) -> Option<Lum<T>> {
        Some(Lum::from_color(color))
    }
}

impl<F, T> FromColor<Option<Rgb<F>>> for Option<Lum<T>>
    where Rgb<F>: Luminance, f64: ClampInto<T>
{
    fn from_color (color: Option<Rgb<F>>) -> Option<Lum<T>> {
        match color {
            None => None,
            Some(color) => Some(Lum::from_color(color)),
        }
    }
}

impl<F, T> FromColor<Rgba<F>> for Option<Lum<T>>
    where Rgb<F>: Luminance, f64: ClampInto<T>, F: PartialOrd + Clamp
{
    fn from_color (color: Rgba<F>) -> Option<Lum<T>> {
        if color.a > F::clamp_min() {
            Some(Lum::from_color(color.drop_alpha()))
        } else {
            None
        }
    }
}

// Conversion to Luma<T>

impl<F, T> FromColor<Lum<F>> for Luma<T>
    where F: ClampInto<T>, T: Clamp
{
    fn from_color (color: Lum<F>) -> Luma<T> {
        Lum::from_color(color).with_alpha(T::clamp_max())
    }
}

impl<F, T> FromColor<Option<Lum<F>>> for Luma<T>
    where F: ClampInto<T>, T: Clamp
{
    fn from_color (color: Option<Lum<F>>) -> Luma<T> {
        match color {
            None => Luma::clamp_min(),
            Some(color) => Lum::from_color(color).with_alpha(T::clamp_max()),
        }
    }
}

impl<F, T> FromColor<Luma<F>> for Luma<T>
    where F: ClampInto<T>
{
    fn from_color (color: Luma<F>) -> Luma<T> {
        Luma { l: color.l.clamp_into(), a: color.a.clamp_into() }
    }
}

impl<F, T> FromColor<Rgb<F>> for Luma<T>
    where Rgb<F>: Luminance, f64: ClampInto<T>, T: Clamp
{
    fn from_color (color: Rgb<F>) -> Luma<T> {
        Lum::from_color(color).with_alpha(T::clamp_max())
    }
}

impl<F, T> FromColor<Option<Rgb<F>>> for Luma<T>
    where Rgb<F>: Luminance, f64: ClampInto<T>, T: Clamp
{
    fn from_color (color: Option<Rgb<F>>) -> Luma<T> {
        match color {
            None => Luma::clamp_min(),
            Some(color) => Lum::from_color(color).with_alpha(T::clamp_max()),
        }
    }
}

impl<F, T> FromColor<Rgba<F>> for Luma<T>
    where Rgb<F>: Luminance, f64: ClampInto<T>, F: ClampInto<T>
{
    fn from_color (color: Rgba<F>) -> Luma<T> {
        let (rgb, a) = color.split_alpha();
        Lum::from_color(rgb).with_alpha(a.clamp_into())
    }
}

// Conversion to Rgb<T>

impl<F, T> FromColor<Lum<F>> for Rgb<T>
    where F: ClampInto<T>, T: Clone
{
    fn from_color (color: Lum<F>) -> Rgb<T> {
        Rgb::from_lum(color.l.clamp_into())
    }
}

impl<F, T> FromColor<Option<Lum<F>>> for Rgb<T>
    where F: ClampInto<T>, T: Clone + Clamp
{
    fn from_color (color: Option<Lum<F>>) -> Rgb<T> {
        match color {
            None => Rgb::clamp_min(),
            Some(color) => Rgb::from_color(color),
        }
    }
}

impl<F, T> FromColor<Luma<F>> for Rgb<T>
    where F: ClampInto<T>, T: Clone
{
    fn from_color (color: Luma<F>) -> Rgb<T> {
        Rgb::from_color(color.drop_alpha())
    }
}

impl<F, T> FromColor<Rgb<F>> for Rgb<T>
    where F: ClampInto<T>
{
    fn from_color (color: Rgb<F>) -> Rgb<T> {
        Rgb {
            r: color.r.clamp_into(),
            g: color.g.clamp_into(),
            b: color.b.clamp_into(),
        }
    }
}

impl<F, T> FromColor<Option<Rgb<F>>> for Rgb<T>
    where F: ClampInto<T>, T: Clamp
{
    fn from_color (color: Option<Rgb<F>>) -> Rgb<T> {
        match color {
            None => Rgb::clamp_min(),
            Some(color) => Rgb::from_color(color),
        }
    }
}

impl<F, T> FromColor<Rgba<F>> for Rgb<T>
    where F: ClampInto<T>
{
    fn from_color (color: Rgba<F>) -> Rgb<T> {
        Rgb::from_color(color.drop_alpha())
    }
}

// Conversion to Option<Rgb<T>>

impl<F, T> FromColor<Lum<F>> for Option<Rgb<T>>
    where F: ClampInto<T>, T: Clone
{
    fn from_color (color: Lum<F>) -> Option<Rgb<T>> {
        Some(Rgb::from_color(color))
    }
}

impl<F, T> FromColor<Option<Lum<F>>> for Option<Rgb<T>>
    where F: ClampInto<T>, T: Clone
{
    fn from_color (color: Option<Lum<F>>) -> Option<Rgb<T>> {
        match color {
            None => None,
            Some(color) => Some(Rgb::from_color(color)),
        }
    }
}

impl<F, T> FromColor<Luma<F>> for Option<Rgb<T>>
    where F: PartialOrd + Clamp + ClampInto<T>, T: Clone
{
    fn from_color (color: Luma<F>) -> Option<Rgb<T>> {
        if color.a > F::clamp_min() {
            Some(Rgb::from_color(color))
        } else {
            None
        }
    }
}

impl<F, T> FromColor<Rgb<F>> for Option<Rgb<T>>
    where F: ClampInto<T>
{
    fn from_color (color: Rgb<F>) -> Option<Rgb<T>> {
        Some(Rgb::from_color(color))
    }
}

impl<F, T> FromColor<Option<Rgb<F>>> for Option<Rgb<T>>
    where F: ClampInto<T>
{
    fn from_color (color: Option<Rgb<F>>) -> Option<Rgb<T>> {
        match color {
            None => None,
            Some(color) => Some(Rgb::from_color(color)),
        }
    }
}

impl<F, T> FromColor<Rgba<F>> for Option<Rgb<T>>
    where F: PartialOrd + Clamp + ClampInto<T>
{
    fn from_color (color: Rgba<F>) -> Option<Rgb<T>> {
        if color.a > F::clamp_min() {
            Some(Rgb::from_color(color.drop_alpha()))
        } else {
            None
        }
    }
}

// Conversion to Rgba<T>

impl<F, T> FromColor<Lum<F>> for Rgba<T>
    where F: ClampInto<T>, T: Clone + Clamp
{
    fn from_color (color: Lum<F>) -> Rgba<T> {
        Rgb::from_color(color).with_alpha(T::clamp_max())
    }
}

impl<F, T> FromColor<Option<Lum<F>>> for Rgba<T>
    where F: ClampInto<T>, T: Clone + Clamp
{
    fn from_color (color: Option<Lum<F>>) -> Rgba<T> {
        match color {
            None => Rgba::clamp_min(),
            Some(color) => Rgb::from_color(color).with_alpha(T::clamp_max()),
        }
    }
}

impl<F, T> FromColor<Luma<F>> for Rgba<T>
    where F: ClampInto<T>, T: Clone
{
    fn from_color (color: Luma<F>) -> Rgba<T> {
        let (lum, a) = color.split_alpha();
        Rgb::from_color(lum).with_alpha(a.clamp_into())
    }
}

impl<F, T> FromColor<Rgb<F>> for Rgba<T>
    where F: ClampInto<T>, T: Clamp
{
    fn from_color (color: Rgb<F>) -> Rgba<T> {
        Rgb::from_color(color).with_alpha(T::clamp_max())
    }
}

impl<F, T> FromColor<Option<Rgb<F>>> for Rgba<T>
    where F: ClampInto<T>, T: Clamp
{
    fn from_color (color: Option<Rgb<F>>) -> Rgba<T> {
        match color {
            None => Rgba::clamp_min(),
            Some(color) => Rgba::from_color(color),
        }
    }
}

impl<F, T> FromColor<Rgba<F>> for Rgba<T>
    where F: ClampInto<T>
{
    fn from_color (color: Rgba<F>) -> Rgba<T> {
        Rgba {
            r: color.r.clamp_into(),
            g: color.g.clamp_into(),
            b: color.b.clamp_into(),
            a: color.a.clamp_into(),
        }
    }
}
