"""
This file is a modification of the pygear project (https://sourceforge.net/projects/pygear/)
and is distribuited under the same license of his parent

license:
This code is published under the terms of the GNU General Public License v3
http://www.gnu.org/licenses/gpl-3.0.html
"""
from copy import deepcopy
from math import tan, radians, atan, pi, sin, cos, degrees, acos, asin, sqrt

import numpy as np

from gearbox.libs.maths import involute as inv
from gearbox.libs.maths import rotate, CartesianCoordinatesToPolarCoordinates, sign


class GearWheel(object):
    """
    Parent Class for all gear wheels.
    """

    # Attributes: settings for geometry construction
    points_flank = 100  # points along the involute from root form to tip form circle
    points_fillet = 50  # points on fillet from root to root form circle
    points_tip = 20  # points along tip circle (half tooth)
    points_root = 5  # points along root circle from center of gap to
    # beginning of fillet (half tooth)
    points_chamfer = 20  # points on tip chamfer
    points_ext = 100  # points on extension of involute beyond base circle (if applicable)(should be at least 8)
    points_width = 10  # resolution along width

    # Attributes: default settings for parameters
    _x_default = 0.0  # default value for addendum modification
    _alpha_n_default = 20.0  # default value for pressure angle (DIN 867)
    _beta_default = 0.0  # default value for helix angle (spur gear)
    _rho_f_default = 0.38  # default value for fillet radius (divided bei module)(DIN 867)
    _c_default = 0.25  # default value for tip clearance (divided bei module)(DIN 867)
    _k_default = 0.0  # default value for tip height modification
    _d_s_default = 0.0  # default value for shaft diameter (inner gear wheel diameter)
    _h_k_default = 0.0  # default value for radial value of tip chamfer
    _tol_default = 1e-6  # default tolerance for comparisons
    _A_s_default = 0.0  # default value for tooth thickness allowance (DIN 3967)

    # Attributes: gear data
    data = None  # dictionary containing all gear parameters for macro-geometry
    modifications = None  # dictionary containing all gear parameters for micro-geometry (flank profile modifications)
    formcoords = None  # list of 2D-coordinates of points of half a tooth profile (TColgp_Array1OfPnt2d, pythonOCC)
    _formwire = None  # wire of half a tooth profile (TopoDS_Wire, pythonOCC)

    def set_resolution(self, curvename, value):
        """
        Set resolution for tooth form representation

        INPUT parameters:
        curvename : segment of tooth flank (string)
                    one of the following: flank, fillet, tip, root, shaft, width
        value     : new value for number of points to represent segment
        """

        if curvename == 'flank':
            self.points_flank = value
        elif curvename == 'fillet':
            self.points_fillet = value
        elif curvename == 'tip':
            self.points_tip = value
        elif curvename == 'root':
            self.points_root = value
        elif curvename == 'shaft':
            self.points_shaft = value
        elif curvename == 'width':
            self.points_width = value

    def get_resolution(self, curvename):
        """
        Get resolution for tooth form representation

        INPUT parameters:
        curvename : segment of tooth flank (string)
                    one of the following: flank, fillet, tip, root, shaft, width

        OUTPUT:
        number of points used to represent requested segment
        """

        if curvename == 'flank':
            return self.points_flank
        elif curvename == 'fillet':
            return self.points_fillet
        elif curvename == 'tip':
            return self.points_tip
        elif curvename == 'root':
            return self.points_root
        elif curvename == 'shaft':
            return self.points_shaft
        elif curvename == 'width':
            return self.points_width

    def _make_unique(self, coords):
        """
        Remove redundant entries from coordinate array

        INPUT parameter:
        coords : list of 2d-coordinate points (TColgp_Array1OfPnt2d, pythonOCC)

        OUTPUT:
        unique_coords : list of unique coordinates (TColgp_Array1OfPnt2d, pythonOCC)
        """

        # tolerance for comparisons
        index = None
        tol = self._tol_default * self.data.get('m_n')

        # upper and lower index of point-array
        upper_index = len(coords)
        lower_index = 0

        # remove redundant entries
        uniques = list()
        for index in range(lower_index, upper_index):
            unique = True
            for unique_point in uniques:
                if abs(coords[index][0] - unique_point[0]) < tol and abs(coords[index][1] - unique_point[1]) < tol:
                    unique = False
            if unique:
                uniques.append([coords[index][0], coords[index][1]])

        # copy list entries into coordinate array
        length_uniques = len(uniques)
        unique_coords = {}
        for index in range(lower_index, lower_index + length_uniques):
            if abs(uniques[index - 1][0]) > tol:
                unique_x = uniques[index - 1][0]
            else:
                unique_x = 0.0
            if abs(uniques[index - 1][1]) > tol:
                unique_y = uniques[index - 1][1]
            else:
                unique_y = 0.0
            if unique_x and unique_y:
                unique_coords.update({index: [unique_x, unique_y]})

        unique_coords.update({index + 1: [0.0, self.data['d_a'] / 2]})
        return unique_coords

    def __str__(self):
        """
        Define string conversion of GearWheel objects

        INPUT parameter:
        -

        OUTPUT:
        string representation of class
        """

        outstr = 'gear wheel data:\n'
        # output gear data
        for date in self.data:
            outstr += date.ljust(10) + ':\t' + str(self.data.get(date)) + '\n'

        # output modification data
        if self.modifications:
            outstr += '\nflank modifications:\n'
            for date in self.modifications:
                outstr += date.ljust(10) + ':\t' + str(self.modifications.get(date)) + '\n'

        # output tooth form coord
inates
        if self.formcoords:
            # upper and lower index of point-array
            outstr += '\ntooth form coordinates:\n'
            for coord in self.formcoords:
                outstr += str(coord[0]) + '\t' + str(coord[1]) + '\n'

        return outstr

    def __init__(self, geardata, flankmods=None):
        """
        Initialization of GearW
heel-object
        Should be overwritten in derived classes

        INPUT parameter:
        geardata   : data of gear wheel (dictionary)
        flankmods  : data of flank modifications (dictionary)
        formcoords : list of 2d-coordinate points (list, list(len=2), numeric)
        """

        self.points_shaft = None
        self.data = deepcopy(geardata)
        self.modifications = deepcopy(flankmods)

    def get_gear_data(self):
        """
        Return data-attribute of class

        OUTPUT:
        data attribute of class (dictionary)
        """

        return self.data

    def set_gear_data(self, geardata):
        """
        Set data-attribute of class, overwrite current value

        INPUT parameter:
        geardata : dictionary, containing geometric data of gear
                   for content, see method __init__
        """

        self.__init__(geardata, self.modifications)

    def update_gear_data(self, geardata):
        """
        Set data-attribute of class, update current value

        INPUT parameter:
        geardata : dictionary, containing geometric data of gear
                   for content, see method __init__
        """

        tempdata = self.data.copy()
        tempdata.update(geardata)
        self.__init__(geardata, self.modifications)

    def get_flank_modifications(self):
        """
        Return modifications-attribute of class

        OUTPUT:
        data attribute of class (dictionary)
        """

     