# -*- coding: utf-8 -*-

#  Copyright © 2015  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

from math import sqrt

from pybiklib.debug import DEBUG_INVISIBLE
from pybiklib.modelcommon import epsilon
from .geom import Vector, Polyhedron, Plane, distance_axis_vert, distance_axis_edge, Face


N_ = lambda t: t


class ModelBase:
    bevel_width = 0.1
    
    def __init__(self):
        self.facekeys = [fn.replace(' ', '_') for fn in self.facenames]
        self.cell = self.create_single_cell()
        self.axes = self.create_axes()
        self.normals = {hf.face.id: hf.normal() for hf in self.cell.halffaces}
        
    def create_axes(self):
        hf_by_id = {hf.face.id: hf for hf in self.cell.halffaces}
        return [hf_by_id[s].center().normalised() for s in self.symbols]
        
    def scale_cell(self, polys, sizes):
        saxes = (a*s for a, s in zip(self.axes, sizes))
        scale1 = (sqrt(sum(f*f for f in a)) for a in zip(*self.axes))
        scaleS = (sqrt(sum(f*f for f in a)) for a in zip(*saxes))
        scale = tuple(s/s1 for s, s1 in zip(scaleS, scale1))
        polys.scale(scale)
        
    def split_cell(self, polys, sizes):
        slice_centers = []
        cuts = {}
        for iaxis, (axis, size) in enumerate(zip(self.axes, sizes)):
            slice_centers_axis = []
            axis = Vector(axis)
            plane = Plane(axis)
            split_low, split_high = self.splits(iaxis, size, [0, size])
            for plane.distance in self.splits(iaxis, size, range(1, size)):
                slice_centers_axis.append((split_low+plane.distance)/2)
                split_low = plane.distance
                split_id = polys.split_plane(plane, iaxis)
                cuts[split_id] = axis
            slice_centers_axis.append((split_low+split_high)/2)
            if split_low < split_high:
                slice_centers_axis.reverse()
            slice_centers.append(slice_centers_axis)
        return cuts, slice_centers
        
    @staticmethod
    def mark_invisible_faces(polys, cuts):
        for split_id, axis in cuts.items():
            visible_limit = 10000
            for cell in polys.cells:
                for hf in cell.halffaces:
                    if hf.face.type == 'face':
                        for he in hf.halfedges:
                            other_face = he.other.halfface.face
                            if other_face.type == 'cut' and other_face.id == split_id:
                                dist_ae = distance_axis_edge(axis, he.verts)
                                if dist_ae < visible_limit:
                                    visible_limit = dist_ae
            # mark all faces whose verts are within visible_limit around axis
            for face in polys.faces:
                if face.type == 'cut' and face.id == split_id:
                    for vert in face.verts:
                        dav = distance_axis_vert(axis, vert)
                        if dav > visible_limit - epsilon:
                            break
                    else:
                        face.type += '_removed'
                        
    @classmethod
    def remove_cells(self, polys, cuts):
        self.mark_invisible_faces(polys, cuts)
        if DEBUG_INVISIBLE:
            return
        polys.remove_cells(lambda c: all(f.type.endswith('_removed') for f in c.faces))
        
    def create_cells(self, sizes):
        self.cell.indices = (0,) * len(sizes)
        polys = Polyhedron()
        polys.cells = [self.cell]
        polys = polys.clone()
        self.scale_cell(polys, sizes)
        cuts, slice_centers = self.split_cell(polys, sizes)
        self.remove_cells(polys, cuts)
        return polys, slice_centers
        
        
class BrickModel (ModelBase):
    type = 'Brick'
    fileclass = 'b'
    name = N_('Brick')
    mformat = N_('{0}×{1}×{2}-Brick')
    sizenames = (N_('Width:'), N_('Height:'), N_('Depth:'))
    defaultsize = (4, 2, 3)
    
    symmetries = 2, 2, 2
    symbols = 'LDB'
    symbolsI = 'RUF'
    faces = 'UDLRFB'
    facenames = (N_('Up'), N_('Down'), N_('Left'), N_('Right'), N_('Front'), N_('Back'))
    default_rotation = (-30.,39.)
    
    @classmethod
    def norm_sizes(cls, size):
        y, z, x = sorted(size)
        return (x, y, z), (x, y, z)
        
    @staticmethod
    def create_single_cell():
        #              standard
        #             orientation    face symbols
        # *------*                     +---+
        # |\     |\      y             | U |
        # | *------*     |         +---+---+---+---+
        # | |    | |     o--x      | L | F | R | B |
        # *-|----* |      \        +---+---+---+---+
        #  \|     \|       z           | D |
        #   *------*                   +---+
        up = Face.polygon((-1., -1.), 4, ids='FRBL')
        cell = up.extrude_y(1., -1., ids='UD')
        return cell
        
    def splits(self, iaxis, size, isplits):
        return ((size - 2.*i) for i in isplits)
        
        
class TowerModel (BrickModel):
    type = 'Tower'
    name = N_('Tower')
    mformat = N_('{0}×{1}-Tower')
    sizenames = (N_('Basis:'), N_('Height:'))
    defaultsize = (2, 3)
    symmetries = 2, 4, 2
    
    @classmethod
    def norm_sizes(cls, size):
        x, y = size
        return (x, y), (x, y, x)
        
        
class CubeModel (BrickModel):
    type = 'Cube'
    name = N_('Cube')
    mformat = N_('{0}×{0}×{0}-Cube')
    sizenames = (N_('Size:'),)
    defaultsize = (3,)
    symmetries = 4, 4, 4
    
    @classmethod
    def norm_sizes(cls, size):
        x, = size
        return (x,), (x, x, x)
        
        
class VoidCubeModel (BrickModel):
    type = 'Cube3Void'
    name = N_('Void Cube')
    mformat = N_('Void Cube')
    sizenames = ()
    defaultsize = ()
    symmetries = 4, 4, 4
    
    @classmethod
    def norm_sizes(cls, unused_size):
        return (), (3, 3, 3)
        
    @classmethod
    def remove_cells(self, polys, cuts):
        polys.remove_cells(lambda c: sum(int(i==1) for i in c.indices) >= 2)
        
        
class TetrahedronModel (ModelBase):
    type = 'Tetrahedron'
    fileclass = 't'
    name = N_('Tetrahedron')
    mformat = N_('{0}-Tetrahedron')
    sizenames = (N_('Size:'),)
    defaultsize = (3,)
    
    symmetries = 3, 3, 3, 3
    symbols = 'LDBR'
    symbolsI = 'WXYZ'
    faces = 'DLRB'
    facenames = (N_('Down'), N_('Left'), N_('Right'), N_('Back'))
    default_rotation = (-10.,39.)
    
    @classmethod
    def norm_sizes(cls, size):
        x, = size
        return (x,), (x, x, x, x)
        
    _redge = sqrt(3/2)
    _itriangle = sqrt(2) / 2
    _rtriangle = sqrt(2)
    _itetrahedron = 1 / 2
    _rtetrahedron = 3 / 2
    
    _verts = [Vector((-_redge, -_itetrahedron, -_itriangle)),
              Vector(( _redge, -_itetrahedron, -_itriangle)),
              Vector(( 0.,      _rtetrahedron, 0.)),
              Vector(( 0.,     -_itetrahedron, _rtriangle))]
              
    @classmethod
    def create_single_cell(self):
        #  vertex      standard
        #  indices    orientation    face symbols
        # 2
        # |              y
        # |              |             +-----+
        # |              o--x        /L|B\ R/
        # 0------1        \        +---+---+
        #  \               z           |D/
        #   3                          +
        down = Face.polygon((0., -self._rtriangle), 3, ids='LBR')
        cell = down.pyramid(1.5, -.5, id='D')
        return cell
        
    def splits(self, iaxis, size, isplits):
        return ((i*2 - self._rtetrahedron*size) for i in isplits)
        
        
class TriangularPrism (ModelBase):
    type = 'Prism3'
    fileclass = 't'
    name = N_('Triangular Prism')
    mformat = N_('{0}×{1} Triangular Prism')
    sizenames = (N_('Basis:'), N_('Height:'))
    defaultsize = (3, 2)
    
    symmetries = 2, 3, 2, 2
    symbols = 'LDBR'
    symbolsI = 'XUYZ'
    faces = 'UDLRB'
    facenames = (N_('Up'), N_('Down'), N_('Left'), N_('Right'), N_('Back'))
    
    default_rotation = (-4.,39.)
    
    @classmethod
    def norm_sizes(cls, size):
        b, h = size
        if b > 6 or h > 6:
            return None, None
        return (b, h), (b, h, b, b)
        
    _rtriangle = sqrt(2)
    _itriangle = _rtriangle / 2
    
    @classmethod
    def create_single_cell(self):
        #              standard      polygon
        #             orientation  orientation
        # *------*
        # |\    /|       y          y
        # |  *   |       |          |
        # |  |   |       o--x       |
        # *--|---*        \         o-----x
        #  \ |  /          z
        #    *
        up = Face.polygon((0., -self._rtriangle), 3, ids='RBL')
        cell = up.extrude_y(1., -1., ids='UD')
        return cell
        
    split_arg = 0
    
    def splits(self, iaxis, size, isplits):
        if iaxis == 1:
            return ((i*2. - size) for i in isplits)
        else:
            low = self._rtriangle*size
            d = (self._itriangle + self._rtriangle) * (size/(size+self.split_arg))
            return ((i*d - low) for i in isplits)
            
    
class TriangularPrismComplex (TriangularPrism):
    type = 'Prism3Complex'
    fileclass = 't'
    name = N_('Triangular Prism (complex)')
    mformat = N_('{0}×{1} Triangular Prism (complex)')
    sizenames = (N_('Basis:'), N_('Height:'))
    defaultsize = (3, 2)
    
    split_arg = -1/3
    
    
class PentagonalPrism (ModelBase):
    type = 'Prism5'
    fileclass = 'p'
    name = N_('Pentagonal Prism')
    mformat = N_('{0}×{1} Pentagonal Prism')
    sizenames = (N_('Basis:'), N_('Height:'))
    defaultsize = (2, 2)
    
    symmetries = 2, 5, 2, 2, 2, 2
    symbols =  'AUBCDE'
    symbolsI = 'GLHIJK'
    faces = 'ULABCDE'
    facenames = (N_('Up'), N_('Down'),
                 N_('Front Right'), N_('Right'), N_('Back'), N_('Left'), N_('Front Left'))
    
    default_rotation = (-4.,39.)
    
    @classmethod
    def norm_sizes(cls, size):
        b, h = size
        if b > 6 or h > 6:
            return None, None
        return (b, h), (b, h, b, b, b, b)
        
    def create_single_cell(self):
        up = Face.polygon(Vector((0, -1.4)), 5, ids='ABCDE')
        self._ipentagon = up.halffaces[0].halfedge.center().length()
        upverts = iter(up.halffaces[0].verts)
        v1, unused, v3  = next(upverts), next(upverts), next(upverts)
        d3pentagon = (v1.point + v3.point).length() / 2
        self._hpentagon = (self._ipentagon - d3pentagon)/2
        
        cell = up.extrude_y(1., -1., ids='UL')
        return cell
        
    side_center = 0
        
    def splits(self, iaxis, size, isplits):
        if iaxis == 1:
            return ((i*2. - size) for i in isplits)
        else:
            dpentagon = self._ipentagon - self._hpentagon
            d = dpentagon*size/max(size-1+self.side_center, 1)
            low = self._hpentagon*size - (1-self.side_center)*d
            # values for i==0 is the same as for i==size.
            # its on the wrong side of the prism, but then most faces
            # have only 4 directions in quadrant mode
            return ((i*d + low if i>0 else size*self._ipentagon) for i in isplits)
            
        
class PentagonalPrismM (PentagonalPrism):
    type = 'Prism5M'
    fileclass = 'p'
    name = N_('Pentagonal Prism (stretched)')
    mformat = N_('{0}×{1} Pentagonal Prism (stretched)')
    
    side_center = 0.2
            
        
modeldefs = [CubeModel, TowerModel, BrickModel, TetrahedronModel, VoidCubeModel,
             TriangularPrism, TriangularPrismComplex,
             PentagonalPrism, PentagonalPrismM,
             ]
             

