use crate::{ray::Ray, volumes::scaled::Scaled};
use std::fmt::Debug;
use vek::*;

/// Used to specify a volume's compile-time size. This exists as a substitute
/// until const generics are implemented.
pub trait VolSize: Clone {
    const SIZE: Vec3<u32>;
}

pub trait RectVolSize: Clone {
    const RECT_SIZE: Vec2<u32>;
}

/// A voxel.
pub trait Vox: Sized + Clone + PartialEq {
    fn empty() -> Self;
    fn is_empty(&self) -> bool;

    fn or(self, other: Self) -> Self { if self.is_empty() { other } else { self } }
}

/// A volume that contains voxel data.
pub trait BaseVol {
    type Vox;
    type Error: Debug;

    fn scaled_by(self, scale: Vec3<f32>) -> Scaled<Self>
    where
        Self: Sized,
    {
        Scaled { inner: self, scale }
    }
}

/// Implementing `BaseVol` for any `&'a BaseVol` makes it possible to implement
/// `IntoVolIterator` for references.
impl<'a, T: BaseVol> BaseVol for &'a T {
    type Error = T::Error;
    type Vox = T::Vox;
}

// Utility types

/// A volume that is a cuboid.
pub trait SizedVol: BaseVol {
    /// Returns the (inclusive) lower bound of the volume.
    fn lower_bound(&self) -> Vec3<i32>;

    /// Returns the (exclusive) upper bound of the volume.
    fn upper_bound(&self) -> Vec3<i32>;

    /// Returns the size of the volume.
    fn size(&self) -> Vec3<u32> { (self.upper_bound() - self.lower_bound()).map(|e| e as u32) }
}

/// A volume that is compile-time sized and has its lower bound at `(0, 0, 0)`.
/// The name `RasterableVol` was chosen because such a volume can be used with
/// `VolGrid3d`.
pub trait RasterableVol: BaseVol {
    const SIZE: Vec3<u32>;
}

impl<V: RasterableVol> SizedVol for V {
    fn lower_bound(&self) -> Vec3<i32> { Vec3::zero() }

    fn upper_bound(&self) -> Vec3<i32> { V::SIZE.map(|e| e as i32) }
}

/// A volume whose cross section with the XY-plane is a rectangle.
pub trait RectSizedVol: BaseVol {
    fn lower_bound_xy(&self) -> Vec2<i32>;

    fn upper_bound_xy(&self) -> Vec2<i32>;

    fn size_xy(&self) -> Vec2<u32> {
        (self.upper_bound_xy() - self.lower_bound_xy()).map(|e| e as u32)
    }
}

/// A volume that is compile-time sized in x and y direction and has its lower
/// bound at `(0, 0, z)`. In z direction there's no restriction on the lower
/// or upper bound. The name `RectRasterableVol` was chosen because such a
/// volume can be used with `VolGrid2d`.
pub trait RectRasterableVol: BaseVol {
    const RECT_SIZE: Vec2<u32>;
}

impl<V: RectRasterableVol> RectSizedVol for V {
    fn lower_bound_xy(&self) -> Vec2<i32> { Vec2::zero() }

    fn upper_bound_xy(&self) -> Vec2<i32> { V::RECT_SIZE.map(|e| e as i32) }
}

/// A volume that provides read access to its voxel data.
pub trait ReadVol: BaseVol {
    /// Get a reference to the voxel at the provided position in the volume.
    fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Error>;

    #[allow(clippy::type_complexity)] // TODO: Pending review in #587
    /// NOTE: By default, this ray will simply run from `from` to `to` without
    /// stopping.  To make something interesting happen, call `until` or
    /// `for_each`.
    fn ray(
        &self,
        from: Vec3<f32>,
        to: Vec3<f32>,
    ) -> Ray<Self, fn(&Self::Vox) -> bool, fn(&Self::Vox, Vec3<i32>)>
    where
        Self: Sized,
    {
        Ray::new(self, from, to, |_| true)
    }
}

/// A volume that provides the ability to sample (i.e., clone a section of) its
/// voxel data.
///
/// TODO (haslersn): Do we still need this now that we have `IntoVolIterator`?
pub trait SampleVol<I>: BaseVol {
    type Sample: BaseVol + ReadVol;
    /// Take a sample of the volume by cloning voxels within the provided range.
    ///
    /// Note that value and accessibility of voxels outside the bounds of the
    /// sample is implementation-defined and should not be used.
    ///
    /// Note that the resultant volume has a coordinate space relative to the
    /// sample, not the original volume.
    fn sample(&self, range: I) -> Result<Self::Sample, Self::Error>;
}

/// A volume that provides write access to its voxel data.
pub trait WriteVol: BaseVol {
    /// Set the voxel at the provided position in the volume to the provided
    /// value.
    fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Error>;

    /// Map a voxel to another using the provided function.
    // TODO: Is `map` the right name? Implies a change in type.
    fn map<F: FnOnce(Self::Vox) -> Self::Vox>(
        &mut self,
        pos: Vec3<i32>,
        f: F,
    ) -> Result<(), Self::Error>
    where
        Self: ReadVol,
        Self::Vox: Clone,
    {
        // This is *deliberately* not using a get_mut since this might trigger a
        // repr change of the underlying volume
        self.set(pos, f(self.get(pos)?.clone()))
    }
}

/// A volume (usually rather a reference to a volume) that is convertible into
/// an iterator to a cuboid subsection of the volume.
pub trait IntoVolIterator<'a>: BaseVol
where
    Self::Vox: 'a,
{
    type IntoIter: Iterator<Item = (Vec3<i32>, &'a Self::Vox)>;

    fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter;
}

pub trait IntoPosIterator: BaseVol {
    type IntoIter: Iterator<Item = Vec3<i32>>;

    fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter;
}

// Helpers

/// A volume (usually rather a reference to a volume) that is convertible into
/// an iterator.
pub trait IntoFullVolIterator<'a>: BaseVol
where
    Self::Vox: 'a,
{
    type IntoIter: Iterator<Item = (Vec3<i32>, &'a Self::Vox)>;

    fn full_vol_iter(self) -> Self::IntoIter;
}

/// For any `&'a SizedVol: IntoVolIterator` we implement `IntoFullVolIterator`.
/// Unfortunately we can't just implement `IntoIterator` in this generic way
/// because it's defined in another crate. That's actually the only reason why
/// the trait `IntoFullVolIterator` exists.
// TODO: See whether relaxed orphan rules permit this to be replaced now
impl<'a, T: 'a + SizedVol> IntoFullVolIterator<'a> for &'a T
where
    Self: IntoVolIterator<'a>,
{
    type IntoIter = <Self as IntoVolIterator<'a>>::IntoIter;

    fn full_vol_iter(self) -> Self::IntoIter {
        self.vol_iter(self.lower_bound(), self.upper_bound())
    }
}

pub trait IntoFullPosIterator: BaseVol {
    type IntoIter: Iterator<Item = Vec3<i32>>;

    fn full_pos_iter(self) -> Self::IntoIter;
}

impl<'a, T: 'a + SizedVol> IntoFullPosIterator for &'a T
where
    Self: IntoPosIterator,
{
    type IntoIter = <Self as IntoPosIterator>::IntoIter;

    fn full_pos_iter(self) -> Self::IntoIter {
        self.pos_iter(self.lower_bound(), self.upper_bound())
    }
}

// Defaults

/// Convenience iterator type that can be used to quickly implement
/// `IntoPosIterator`.
pub struct DefaultPosIterator {
    current: Vec3<i32>,
    begin: Vec2<i32>,
    end: Vec3<i32>,
}

impl DefaultPosIterator {
    pub fn new(lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self {
        debug_assert!(lower_bound.map2(upper_bound, |l, u| l <= u).reduce_and());
        let end = if lower_bound.map2(upper_bound, |l, u| l < u).reduce_and() {
            upper_bound
        } else {
            // Special case because our implementation doesn't handle empty ranges for x or
            // y:
            lower_bound
        };
        Self {
            current: lower_bound,
            begin: From::from(lower_bound),
            end,
        }
    }
}

impl Iterator for DefaultPosIterator {
    type Item = Vec3<i32>;

    fn next(&mut self) -> Option<Vec3<i32>> {
        if self.current.z == self.end.z {
            return None;
        }
        let ret = self.current;
        self.current.x += 1;
        if self.current.x == self.end.x {
            self.current.x = self.begin.x;
            self.current.y += 1;
            if self.current.y == self.end.y {
                self.current.y = self.begin.y;
                self.current.z += 1;
            }
        }
        Some(ret)
    }
}

/// Convenience iterator type that can be used to quickly implement
/// `IntoVolIterator`.
pub struct DefaultVolIterator<'a, T: ReadVol> {
    vol: &'a T,
    pos_iter: DefaultPosIterator,
}

impl<'a, T: ReadVol> DefaultVolIterator<'a, T> {
    pub fn new(vol: &'a T, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self {
        Self {
            vol,
            pos_iter: DefaultPosIterator::new(lower_bound, upper_bound),
        }
    }
}

impl<'a, T: ReadVol> Iterator for DefaultVolIterator<'a, T> {
    type Item = (Vec3<i32>, &'a T::Vox);

    fn next(&mut self) -> Option<(Vec3<i32>, &'a T::Vox)> {
        while let Some(pos) = self.pos_iter.next() {
            if let Ok(vox) = self.vol.get(pos) {
                return Some((pos, vox));
            }
        }
        None
    }
}

impl<'b, T: ReadVol> ReadVol for &'b T {
    #[inline(always)]
    fn get(&self, pos: Vec3<i32>) -> Result<&'_ Self::Vox, Self::Error> { (*self).get(pos) }
}
