#!/usr/bin/python3
# -*- coding: utf-8 -*-

#  Copyright © 2014-2015, 2017  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/>.


import sys
from math import sqrt, atan2


epsdigits = 5
epsilon = 10**-epsdigits
filebyteorder = 'little'

def get_texcoords_range(verts, normal):
    texcoords = [vert.rotationyx_normal(normal) for vert in verts]
    minx = min(x for x, y in texcoords)
    maxx = max(x for x, y in texcoords)
    miny = min(y for x, y in texcoords)
    maxy = max(y for x, y in texcoords)
    return minx, maxx, miny, maxy
    
    
class Coords (tuple):
    def __new__(cls, args=(0,0,0)):
        return tuple.__new__(cls, args)
        
    def __add__(self, other):
        return self.__class__(s+o for s, o in zip(self, other))
        
    def __sub__(self, other):
        return self.__class__(s-o for s, o in zip(self, other))
        
    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, super().__repr__())
    def __str__(self):
        return str(list(self))
        
    
class Vector(Coords):
    def __neg__(self):
        return self.__class__(-s for s in self)
        
    def __mul__(self, value):
        return self.__class__(s*value for s in self)
        
    def __truediv__(self, value):
        return self.__class__(s/value for s in self)
        
    def cross(self, other):
        return self.__class__((
                    self[1] * other[2] - other[1] * self[2],
                    self[2] * other[0] - other[2] * self[0],
                    self[0] * other[1] - other[0] * self[1]))
                    
    def dot(self, other): return sum(s*o for s, o in zip(self, other))
    def length_squared(self): return self.dot(self)
    def length(self): return sqrt(self.length_squared())
    def normalised(self): return self / self.length()
    
    def angle(self, other):
        sina = self.cross(other).length()
        cosa = self.dot(other)
        return atan2(sina, cosa)
        
    def angle_plane(self, other, plane_normal):
        cross = self.cross(other)
        sina = cross.length()
        cosa = self.dot(other)
        angle = atan2(sina, cosa)
        if cross.dot(plane_normal) < 0:
            return - angle
        return angle
        
    def anglex(self):
        sina = -self[1]
        cosa = -self[2]
        if sina*sina + cosa*cosa < epsilon:
            cosa, sina = 1, 0
        return cosa, sina
        
    def angley(self):
        sina = -self[0]
        cosa = self[2]
        if sina*sina + cosa*cosa < epsilon:
            cosa, sina = 1, 0
        return cosa, sina
        
    def rotationx(self, cosa, sina):
        return self.__class__((self[0], cosa*self[1] - sina*self[2], sina*self[1] + cosa*self[2]))
        
    def rotationy(self, cosa, sina):
        return self.__class__((cosa*self[0] + sina*self[2], self[1], cosa*self[2] - sina*self[0]))
        
    def angleyx(self):
        ''' Same as:
            cosay, sinay = self.angley()
            cosax, sinax = self.rotationy(cosay, sinay).anglex()
            return cosay, sinay, cosax, sinax
        '''
        x, y, z = self
        xx = x*x
        zz = z*z
        xx_zz = xx + zz
        if xx_zz < epsilon:
            if y*y + zz < epsilon:
                return 1, 0, 1, 0
            else:
                return 1, 0, -z, -y
        else:
            if y*y + xx_zz*xx_zz < epsilon:
                return z, -x, 1, 0
            else:
                return z, -x, -xx_zz, -y
                
    def rotationyx(self, cosay, sinay, cosax, sinax):
        x, y, z = self
        x, z = cosay*x + sinay*z, cosay*z - sinay*x
        y, z = cosax*y - sinax*z, sinax*y + cosax*z
        return self.__class__((x, y, z))
        
    def rotationyx_normal(self, normal):
        ''' Same as:
            self.rotationyx(*normal.angleyx())
        '''
        vx, vy, vz = self
        nx, ny, nz = normal
        nxx = nx*nx
        nzz = nz*nz
        nxx_zz = nxx + nzz
        if nxx_zz < epsilon:
            if ny*ny + nzz < epsilon:
                pass
            else:
                vy = ny*vz - nz*vy
        else:
            if ny*ny + nxx_zz*nxx_zz < epsilon:
                vx = nz*vx - nx*vz
            else:
                vy = ny*(nz*vz + nx*vx) - nxx_zz*vy
                vx = nz*vx - nx*vz
        return vx, vy
        
        
def exc_end(func, *, name=None):
    def _func(*args):
        try:
            return func(*args)
        except:
            sys.excepthook(*sys.exc_info())
    _func.__doc__ = func.__doc__
    _func.__module__ = func.__module__
    _func.__name__ = name or func.__name__
    return _func
    
class ExceptionMeta (type):
    def __new__(cls, name, bases, namespace):
        for k, v in sorted(namespace.items()):
            if k.startswith('on_'):
                namespace[k] = exc_end(v, name=k)
        return type(name, bases, namespace)
        

