// 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 std::{u8, u16, u32, u64};

/// Trait for types which can be clamped between a minimum and maximum acceptable value.
pub trait Clamp : Sized {
    fn clamp_min () -> Self;
    fn clamp_max () -> Self;

    fn clamp (self) -> Self
        where Self: PartialOrd
    {
        let min = Self::clamp_min();

        if self < min {
            return min;
        }

        let max = Self::clamp_max();

        if self > max {
            max
        } else {
            self
        }
    }
}

macro_rules! impl_clamp_uint {
    ($($ty:ident),*) => { $(
        impl Clamp for $ty {
            #[inline(always)]
            fn clamp_min () -> $ty { 0 }

            #[inline(always)]
            fn clamp_max () -> $ty { $ty::MAX }

            #[inline(always)]
            fn clamp (self) -> $ty { self }
        }
    )* };
}

macro_rules! impl_clamp_float {
    ($($ty:ty),*) => { $(
        impl Clamp for $ty {
            #[inline(always)]
            fn clamp_min () -> $ty { 0.0 }

            #[inline(always)]
            fn clamp_max () -> $ty { 1.0 }
        }
    )* };
}

impl_clamp_uint!(u8, u16, u32, u64);
impl_clamp_float!(f32, f64);

/// Interpolate from another clamped type
pub trait ClampFrom<F> {
    fn clamp_from (n: F) -> Self;
}

macro_rules! impl_clamp_from_self {
    ($($ty:ty),*) => { $(
        impl ClampFrom<$ty> for $ty {
            #[inline(always)]
            fn clamp_from (n: $ty) -> $ty { n }
        }
    )* };
}

impl_clamp_from_self!(u8, u16, u32, u64, f32, f64);

impl ClampFrom<u16> for u8 {
    #[inline(always)]
    fn clamp_from (n: u16) -> u8 { (n >> 8) as u8 }
}

impl ClampFrom<u32> for u8 {
    #[inline(always)]
    fn clamp_from (n: u32) -> u8 { (n >> 24) as u8 }
}

impl ClampFrom<u64> for u8 {
    #[inline(always)]
    fn clamp_from (n: u64) -> u8 { (n >> 56) as u8 }
}

impl ClampFrom<u8> for u16 {
    #[inline(always)]
    fn clamp_from (n: u8) -> u16 { n as u16 * 0x0101 }
}

impl ClampFrom<u32> for u16 {
    #[inline(always)]
    fn clamp_from (n: u32) -> u16 { (n >> 16) as u16 }
}

impl ClampFrom<u64> for u16 {
    #[inline(always)]
    fn clamp_from (n: u64) -> u16 { (n >> 48) as u16 }
}

impl ClampFrom<u8> for u32 {
    #[inline(always)]
    fn clamp_from (n: u8) -> u32 { n as u32 * 0x0101_0101 }
}

impl ClampFrom<u16> for u32 {
    #[inline(always)]
    fn clamp_from (n: u16) -> u32 { n as u32 * 0x0001_0001 }
}

impl ClampFrom<u64> for u32 {
    #[inline(always)]
    fn clamp_from (n: u64) -> u32 { (n >> 32) as u32 }
}

impl ClampFrom<u8> for u64 {
    #[inline(always)]
    fn clamp_from (n: u8) -> u64 { n as u64 * 0x0101_0101_0101_0101 }
}

impl ClampFrom<u16> for u64 {
    #[inline(always)]
    fn clamp_from (n: u16) -> u64 { n as u64 * 0x0001_0001_0001_0001 }
}

impl ClampFrom<u32> for u64 {
    #[inline(always)]
    fn clamp_from (n: u32) -> u64 { n as u64 * 0x0000_0001_0000_0001 }
}

impl ClampFrom<f64> for f32 {
    #[inline(always)]
    fn clamp_from (n: f64) -> f32 { n as f32 }
}

impl ClampFrom<f32> for f64 {
    #[inline(always)]
    fn clamp_from (n: f32) -> f64 { n as f64 }
}

macro_rules! impl_clamp_int_to_float {
    { $($($int:ident),+ -> $float:ident;)* } => { $($(
        impl ClampFrom<$int> for $float {
            fn clamp_from (n: $int) -> $float {
                n.clamp() as $float / $int::clamp_max() as $float
            }
        }
    )*)* };
}

macro_rules! impl_clamp_float_to_int {
    { $($float:ident -> $($int:ident),+;)* } => { $($(
        impl ClampFrom<$float> for $int {
            fn clamp_from (n: $float) -> $int {
                (n.clamp() * $int::clamp_max() as $float) as $int
            }
        }
    )*)* };
}

impl_clamp_int_to_float! {
    u8, u16, u32, u64 -> f32;
    u8, u16, u32, u64 -> f64;
}

impl_clamp_float_to_int! {
    f32 -> u8, u16, u32, u64;
    f64 -> u8, u16, u32, u64;
}

/// Interpolate into another clamped type.
pub trait ClampInto<T> {
    fn clamp_into (self) -> T;
}

impl<F, T> ClampInto<T> for F
    where T: ClampFrom<F>
{
    fn clamp_into (self) -> T { T::clamp_from(self) }
}
