use crate::index::Index;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::ops::{Add, Div, Mul, Rem, Sub};
pub trait ElementIndex<T>:
    Copy
    + Clone
    + PartialEq
    + PartialOrd
    + Eq
    + Ord
    + From<T>
    + Into<T>
    + Add<Output = Self>
    + Add<T, Output = Self>
    + Sub<Output = Self>
    + Sub<T, Output = Self>
    + Mul<T, Output = Self>
    + Div<T, Output = Self>
    + Rem<T, Output = Self>
{
}
macro_rules! impl_index_type {
    ($index_type:ident) => {
        
        #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
        #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
        pub struct $index_type(usize);
        impl $index_type {
            #[inline]
            pub fn into_inner(self) -> usize {
                self.0
            }
        }
        impl ElementIndex<usize> for $index_type {}
        
        impl Add for $index_type {
            type Output = $index_type;
            #[inline]
            fn add(self, rhs: $index_type) -> $index_type {
                $index_type(self.0 + rhs.0)
            }
        }
        impl Add<$index_type> for usize {
            type Output = $index_type;
            #[inline]
            fn add(self, rhs: $index_type) -> $index_type {
                rhs + self
            }
        }
        impl Add<usize> for $index_type {
            type Output = $index_type;
            #[inline]
            fn add(self, rhs: usize) -> $index_type {
                $index_type(self.0 + rhs)
            }
        }
        impl Sub for $index_type {
            type Output = $index_type;
            #[inline]
            fn sub(self, rhs: $index_type) -> $index_type {
                $index_type(self.0 - rhs.0)
            }
        }
        impl Sub<usize> for $index_type {
            type Output = $index_type;
            #[inline]
            fn sub(self, rhs: usize) -> $index_type {
                $index_type(self.0 - rhs)
            }
        }
        impl Sub<$index_type> for usize {
            type Output = $index_type;
            #[inline]
            fn sub(self, rhs: $index_type) -> $index_type {
                $index_type(self - rhs.0)
            }
        }
        impl Mul<usize> for $index_type {
            type Output = $index_type;
            #[inline]
            fn mul(self, rhs: usize) -> $index_type {
                $index_type(self.0 * rhs)
            }
        }
        impl Mul<$index_type> for usize {
            type Output = $index_type;
            #[inline]
            fn mul(self, rhs: $index_type) -> $index_type {
                rhs * self
            }
        }
        
        impl Div<usize> for $index_type {
            type Output = $index_type;
            #[inline]
            fn div(self, rhs: usize) -> $index_type {
                $index_type(self.0 / rhs)
            }
        }
        impl Rem<usize> for $index_type {
            type Output = $index_type;
            #[inline]
            fn rem(self, rhs: usize) -> $index_type {
                $index_type(self.0 % rhs)
            }
        }
        
        impl From<$index_type> for Index {
            #[inline]
            fn from(i: $index_type) -> Self {
                Index::new(i.0)
            }
        }
        impl From<Option<$index_type>> for Index {
            #[inline]
            fn from(mb_i: Option<$index_type>) -> Self {
                Index::from(mb_i.map(|i| i.0))
            }
        }
        impl From<Index> for $index_type {
            #[inline]
            fn from(i: Index) -> Self {
                $index_type(i.into_inner())
            }
        }
        impl From<$index_type> for usize {
            #[inline]
            fn from(i: $index_type) -> usize {
                i.0
            }
        }
        impl From<usize> for $index_type {
            #[inline]
            fn from(i: usize) -> Self {
                $index_type(i)
            }
        }
    };
}
impl_index_type!(MeshIndex);
impl_index_type!(VertexIndex);
impl_index_type!(EdgeIndex);
impl_index_type!(FaceIndex);
impl_index_type!(CellIndex);
impl_index_type!(EdgeVertexIndex);
impl_index_type!(FaceVertexIndex);
impl_index_type!(FaceEdgeIndex);
impl_index_type!(CellVertexIndex);
impl_index_type!(CellEdgeIndex);
impl_index_type!(CellFaceIndex);
impl_index_type!(VertexEdgeIndex);
impl_index_type!(VertexFaceIndex);
impl_index_type!(VertexCellIndex);
impl_index_type!(EdgeFaceIndex);
impl_index_type!(EdgeCellIndex);
impl_index_type!(FaceCellIndex);
pub trait TopoIndex<I>: ElementIndex<I> {
    type SrcIndex: ElementIndex<I>;
    type DstIndex: ElementIndex<I>;
}
macro_rules! impl_topo_index {
    ($topo_index:ident, $from_index:ident, $to_index:ident) => {
        impl TopoIndex<usize> for $topo_index {
            type SrcIndex = $from_index;
            type DstIndex = $to_index;
        }
    };
}
impl_topo_index!(EdgeVertexIndex, EdgeIndex, VertexIndex);
impl_topo_index!(FaceVertexIndex, FaceIndex, VertexIndex);
impl_topo_index!(FaceEdgeIndex, FaceIndex, EdgeIndex);
impl_topo_index!(CellVertexIndex, CellIndex, VertexIndex);
impl_topo_index!(CellEdgeIndex, CellIndex, EdgeIndex);
impl_topo_index!(CellFaceIndex, CellIndex, FaceIndex);
impl_topo_index!(VertexEdgeIndex, VertexIndex, EdgeIndex);
impl_topo_index!(VertexFaceIndex, VertexIndex, FaceIndex);
impl_topo_index!(VertexCellIndex, VertexIndex, CellIndex);
impl_topo_index!(EdgeFaceIndex, EdgeIndex, FaceIndex);
impl_topo_index!(EdgeCellIndex, EdgeIndex, CellIndex);
impl_topo_index!(FaceCellIndex, FaceIndex, CellIndex);
pub trait NumMeshes {
    fn num_meshes(&self) -> usize;
}
pub trait NumVertices {
    fn num_vertices(&self) -> usize;
}
pub trait NumEdges {
    fn num_edges(&self) -> usize;
}
pub trait NumFaces {
    fn num_faces(&self) -> usize;
}
pub trait NumCells {
    fn num_cells(&self) -> usize;
}
macro_rules! def_topo_trait {
    (__impl,
        $type:ident :
        $topo_fn:ident,
        $dst_fn:ident,
        $transport_fn:ident,
        $num_fn:ident,
        $num_at_fn:ident;
        $from_index:ident,
        $to_index:ident,
        $topo_index:ident
    ) => {
        
        fn $transport_fn<I>(&self, i: I, k: usize) -> Option<$to_index>
        where
            I: Copy + Into<$from_index>
        {
            self.$topo_fn(i, k).map(|j| self.$dst_fn(j))
        }
        
        fn $dst_fn<I>(&self, i: I) -> $to_index
        where
            I: Copy + Into<$topo_index>;
        
        fn $topo_fn<I>(&self, i: I, k: usize) -> Option<$topo_index>
        where
            I: Copy + Into<$from_index>;
        
        fn $num_fn(&self) -> usize;
        
        fn $num_at_fn<I>(&self, i: I) -> usize
        where
            I: Copy + Into<$from_index>;
    };
    (
        $type:ident :
        $topo_fn:ident,
        $dst_fn:ident,
        $transport_fn:ident,
        $num_fn:ident,
        $num_at_fn:ident;
        $from_index:ident,
        $to_index:ident,
        $topo_index:ident
    ) => {
        pub trait $type {
            def_topo_trait!(__impl, $type: $topo_fn, $dst_fn, $transport_fn, $num_fn, $num_at_fn; $from_index, $to_index, $topo_index);
        }
    };
    (
        $type:ident :
        $topo_fn:ident,
        $dst_fn:ident,
        $transport_fn:ident,
        $num_fn:ident,
        $num_at_fn:ident;
        $from_index:ident,
        $to_index:ident,
        $topo_index:ident,
        $num_from_trait:ident ( $num_from_idx:ident ),
        $num_to_trait:ident ( $num_to_idx:ident )
    ) => {
        pub trait $type {
            def_topo_trait!(__impl, $type: $topo_fn, $dst_fn, $transport_fn, $num_fn, $num_at_fn; $from_index, $to_index, $topo_index);
            
            fn reverse_topo(&self) -> (Vec<usize>, Vec<usize>) where Self: $num_from_trait + $num_to_trait {
                let mut indices: Vec<Vec<usize>> = Vec::new();
                indices.resize(self.$num_to_idx(), Vec::new());
                for src_idx in 0..self.$num_from_idx() {
                    let mut which = 0;
                    while let Some(dest_idx) = self.$transport_fn(src_idx, which) {
                        indices[usize::from(dest_idx)].push(src_idx.into());
                        which += 1;
                    }
                }
                let mut offsets = Vec::with_capacity(self.$num_from_idx());
                offsets.push(0);
                for neighbours in indices.iter() {
                    let last = *offsets.last().unwrap();
                    offsets.push(last + neighbours.len());
                }
                (indices
                 .iter()
                 .flat_map(|x| x.iter().cloned())
                 .collect(),
                 offsets)
            }
            
            
            
            
            
            
            fn reverse_source_topo(&self) -> (Vec<usize>, Vec<usize>) where Self: $num_to_trait {
                let mut indices: Vec<Vec<usize>> = Vec::new();
                indices.resize(self.$num_to_idx(), Vec::new());
                for topo_idx in 0..self.$num_fn() {
                    let dest_idx = self.$dst_fn(topo_idx);
                    indices[usize::from(dest_idx)].push(topo_idx.into());
                }
                let mut offsets = Vec::with_capacity(self.$num_fn());
                offsets.push(0);
                for neighbours in indices.iter() {
                    let last = *offsets.last().unwrap();
                    offsets.push(last + neighbours.len());
                }
                (indices
                 .iter()
                 .flat_map(|x| x.iter().cloned())
                 .collect(),
                 offsets)
            }
        }
    };
}
def_topo_trait!(EdgeVertex: edge_vertex, vertex, edge_to_vertex, num_edge_vertices, num_vertices_at_edge;
                EdgeIndex, VertexIndex, EdgeVertexIndex);
def_topo_trait!(FaceVertex: face_vertex, vertex, face_to_vertex, num_face_vertices, num_vertices_at_face;
                FaceIndex, VertexIndex, FaceVertexIndex, NumFaces ( num_faces ), NumVertices ( num_vertices ));
def_topo_trait!(FaceEdge: face_edge, edge, face_to_edge, num_face_edges, num_edges_at_face;
                FaceIndex, EdgeIndex, FaceEdgeIndex);
def_topo_trait!(CellVertex: cell_vertex, vertex, cell_to_vertex, num_cell_vertices, num_vertices_at_cell;
                CellIndex, VertexIndex, CellVertexIndex, NumCells ( num_cells ), NumVertices ( num_vertices ));
def_topo_trait!(CellEdge: cell_edge, edge, cell_to_edge, num_cell_edges, num_edges_at_cell;
                CellIndex, EdgeIndex, CellEdgeIndex);
def_topo_trait!(CellFace: cell_face, face, cell_to_face, num_cell_faces, num_faces_at_cell;
                CellIndex, FaceIndex, CellFaceIndex);
def_topo_trait!(VertexEdge: vertex_edge, edge, vertex_to_edge, num_vertex_edges, num_edges_at_vertex;
                VertexIndex, EdgeIndex, VertexEdgeIndex);
def_topo_trait!(VertexFace: vertex_face, face, vertex_to_face, num_vertex_faces, num_faces_at_vertex;
                VertexIndex, FaceIndex, VertexFaceIndex);
def_topo_trait!(VertexCell: vertex_cell, cell, vertex_to_cell, num_vertex_cells, num_cells_at_vertex;
                VertexIndex, CellIndex, VertexCellIndex);
def_topo_trait!(EdgeFace: edge_face, face, edge_to_face, num_edge_faces, num_faces_at_edge;
                EdgeIndex, FaceIndex, EdgeFaceIndex);
def_topo_trait!(EdgeCell: edge_cell, cell, edge_to_cell, num_edge_cells, num_cells_at_edge;
                EdgeIndex, CellIndex, EdgeCellIndex);
def_topo_trait!(FaceCell: face_cell, cell, face_to_cell, num_face_cells, num_cells_at_face;
                FaceIndex, CellIndex, FaceCellIndex);