from SpindleAssembly import PositionComponents
import numpy as np
import math


def generate_separation(**kwargs):
    """
    Calculate distance and angle between ipMTs based on the Gaussian distribution with a given mean and standard
    deviation

    :param kwargs: model parameters

    :type kwargs: object

    :return: s: distance between ipMTs

    :rtype: s: float

    :return: alpha: angle between ipMTs in degrees

    :rtype: float
    """
    s = np.random.normal(loc=kwargs['separation'][0], scale=kwargs['separation'][1])
    # Generate angle between MTs
    alpha = np.random.normal(loc=kwargs['angle'][0], scale=kwargs['angle'][1])

    return s, alpha


def check_MT_within_centrosome(pos, **kwargs):
    """
    Add ipMT if its x and y coordinates do not exceed the radius of the
    circular cross-section of a centrosome

    :param pos: (x, y) -> local coordinates of ipMT

    :param kwargs: object

    :return: True or False
    """
    centrosomeRadius = kwargs['CentrosomeRadius']
    x = pos[0]
    y = pos[1]
    if not -centrosomeRadius <= x <= centrosomeRadius \
            and -centrosomeRadius <= y <= centrosomeRadius:
        return False
    return True


def condition_for_fourth_ipMT( alpha2, alpha3, s, pos3 ):
    """
    Condition of positioning the fourth ipMT

    :param alpha2: Angle between ipMTs number 2 and 3

    :type alpha2: float

    :param alpha3: Angle between ipMTs number 3 and 4

    :type alpha3: float

    :param s: Separation between ipMTs

    :type s: float

    :param pos3: (x, y) Position of the third ipMT

    :type pos3: tuple

    :return: y coordinate of the ipMT, mewalpha angle of the ipMT

    :rtype: float, float
    """
    if math.radians(alpha3) >= math.pi - math.radians(alpha2):
        newalpha = math.radians(alpha3) - (math.pi - math.radians(alpha2))
        y = pos3[1] + s * math.sin(newalpha)
    else:
        newalpha = - math.radians(alpha3) + (math.pi - math.radians(alpha2))
        y = pos3[1] - s * math.sin(newalpha)
    return y, newalpha


def add_first_ipMT( i, **kwargs ):
    """
    Add and position the first ipMT within the spindle

    :param i: Sequential number of ipMT

    :type i: int

    :param kwargs: model parameters

    :type kwargs: dict

    :return: MTname, pos, alpha, kwargs

    :rtype: str, tuple, float, dict
    """
    x = -0.018
    y = -0.018
    pos = (x, y)
    kwargs['x'] = x
    kwargs['y'] = y
    kwargs['index'] = 1

    MTname, zBeginPos, zEndPos, l = PositionComponents.assign_ipMTs_right(pos, i, **kwargs)
    pos = (x, y, zBeginPos, zEndPos, l)

    alpha = 0
    return MTname, pos, alpha, kwargs


def add_second_ipMT( pos, alpha1, i, **kwargs ):
    """
    Add and position the second ipMT within the spindle

    :param pos: (x, y) position of the previously added ipMT

    :type pos: tuple

    :param alpha1: Angle of the previously added ipMT

    :type alpha1: float

    :param i: Sequential number of ipMT

    :type i: int

    :param kwargs: model parameters

    :type kwargs: dict

    :return: MTname, pos, alpha, kwargs

    :rtype: str, tuple, float, dict
    """
    # Generate position of the ipMT
    s, alpha = generate_separation(**kwargs)
    x = pos[0] + s * math.cos(math.radians(alpha1))
    y = pos[1] + s * math.sin(math.radians(alpha1))
    pos = (x, y)

    # Correct the position until it is within centrosome
    counter = 0
    while not check_MT_within_centrosome(pos, **kwargs):
        if counter > 1000:
            raise ValueError('Some MTs are located outside of the centrosomes. Consider restarting calculation')
            break
        s, alpha = generate_separation(**kwargs)
        # Position MT
        x = pos[0] + s * math.cos(math.radians(alpha1))
        y = pos[1] + s * math.sin(math.radians(alpha1))
        kwargs['x'] = x
        kwargs['y'] = y
        pos = (x, y)
        counter += 1

    # Add and position ipMT
    kwargs['index'] = 2

    MTname, zBeginPos, zEndPos, l = PositionComponents.assign_ipMTs_left(pos, i, **kwargs)
    pos = (x, y, zBeginPos, zEndPos, l)

    return MTname, pos, alpha, kwargs


def add_third_ipMT( pos2, alpha2, i, **kwargs ):
    """
    Add and position the third ipMT within the spindle

    :param pos2: (x, y) position of the previously added ipMT

    :type pos2: tuple

    :param alpha2: Angle of the previously added ipMT

    :type alpha2: float

    :param i: Sequential number of ipMT

    :type i: int

    :param kwargs: model parameters

    :type kwargs: dict

    :return: MTname, pos, alpha, kwargs

    :rtype: str, tuple, float, dict
    """
    # Generate position of the ipMT
    s, alpha = generate_separation(**kwargs)
    x = pos2[0] - s * math.cos(math.radians(alpha2))
    y = pos2[1] + s * math.sin(math.radians(alpha2))
    pos = (x, y)

    # Correct the position until it is within centrosome
    counter = 0
    while not check_MT_within_centrosome(pos, **kwargs):
        if counter > 1000:
            raise ValueError('Some MTs are located outside of the centrosomes. Consider restarting calculation')
            break
        x = pos2[0] - s * math.cos(math.radians(alpha2))
        y = pos2[1] + s * math.sin(math.radians(alpha2))
        kwargs['x'] = x
        kwargs['y'] = y
        counter += 1

    # Add and position ipMT
    kwargs['index'] = 3

    MTname, zBeginPos, zEndPos, l = PositionComponents.assign_ipMTs_right(pos, i, **kwargs)
    pos = (x, y, zBeginPos, zEndPos, l)

    return MTname, pos, alpha, kwargs


def add_fourth_ipMT( pos3, alpha2, alpha3, i, **kwargs ):
    """
    Add and position the fourth ipMT within the spindle

    :param pos3: (x, y) position of the previously added ipMT

    :type pos3: tuple

    :param alpha2: Angle of ipMT 2

    :type alpha2: float

    :param alpha3: Angle of ipMT 3

    :type alpha3: float

    :param i: Sequential number of ipMT

    :type i: int

    :param kwargs: model parameters

    :type kwargs: dict

    :return: MTname, pos, alpha, kwargs

    :rtype: str, tuple, float, dict
    """
    # Generate position of the ipMT
    s, alpha = generate_separation(**kwargs)
    y, newalpha = condition_for_fourth_ipMT(alpha2, alpha3, s, pos3)
    x = pos3[0] - s * math.cos(newalpha)
    pos = (x, y)

    # Correct the position until it is within centrosome
    counter = 0
    while not check_MT_within_centrosome(pos, **kwargs):
        if counter > 1000:
            raise ValueError('Some MTs are located outside of the centrosomes. Consider restarting calculation')
            break
        y, newalpha = condition_for_fourth_ipMT(alpha2, alpha3, s, pos3)
        x = pos3[0] - s * math.cos(newalpha)
        kwargs['x'] = x
        kwargs['y'] = y
        counter += 1

    # Add and position ipMT
    kwargs['index'] = 4

    MTname, zBeginPos, zEndPos, l = PositionComponents.assign_ipMTs_left(pos, i, **kwargs)
    pos = (x, y, zBeginPos, zEndPos, l)

    return MTname, pos, newalpha, kwargs


def add_fifth_ipMT( pos4, alpha4, i, **kwargs ):
    """
    Add and position the fifth ipMT within the spindle

    :param pos4: (x, y) position of the previously added ipMT

    :type pos4: tuple

    :param alpha4: Angle of the previously added ipMT

    :type alpha4: float

    :param i: Sequential number of ipMT

    :type i: int

    :param kwargs: model parameters

    :type kwargs: dict

    :return: MTname, pos, alpha, kwargs

    :rtype: str, tuple, float, dict
    """
    # Generate position of the ipMT
    s, alpha = generate_separation(**kwargs)
    x = pos4[0] - s * math.cos(-math.radians(alpha4))
    y = pos4[1] + s * math.sin(-math.radians(alpha4))
    pos = (x, y)

    # Correct the position until it is within centrosome
    counter = 0
    while not check_MT_within_centrosome(pos, **kwargs):

        if counter > 1000:
            raise ValueError('Some MTs are located outside of the centrosomes. Consider restarting calculation')
            break

        x = pos4[0] - s * math.cos(-math.radians(alpha4))
        y = pos4[1] + s * math.sin(-math.radians(alpha4))
        kwargs['x'] = x
        kwargs['y'] = y
        counter += 1

    # Add and position ipMT
    kwargs['index'] = 5

    MTname, zBeginPos, zEndPos, l = PositionComponents.assign_ipMTs_right(pos, i, **kwargs)
    pos = (x, y, zBeginPos, zEndPos, l)

    return MTname, pos, alpha, kwargs


def add_sixth_ipMT( pos5, alpha4, alpha5, i, **kwargs ):
    """
    Add and position sixth ipMT within the spindle

    :param pos5: (x, y) position of the previously added ipMT

    :type pos5: tuple

    :param alpha4: Angle of the ipMT 4

    :type alpha4: float

    :param alpha5: Angle of the ipMT 5

    :type alpha5: float

    :param i: Sequential number of ipMT

    :type i: int

    :param kwargs: model parameters

    :type kwargs: dict

    :return: MTname, pos, alpha, kwargs

    :rtype: str, tuple, float, dict
    """
    # Generate position of the ipMT
    s, alpha = generate_separation(**kwargs)
    x = pos5[0] + s * math.cos(-math.radians(alpha5) + math.radians(alpha4))
    y = pos5[1] + s * math.sin(-math.radians(alpha5) + math.radians(alpha4))
    pos = (x, y)

    # Correct the position until it is within centrosome
    counter = 0
    while not check_MT_within_centrosome(pos, **kwargs):
        if counter > 1000:
            raise ValueError('Some MTs are located outside of the centrosomes. Consider restarting calculation')
            break
        x = pos5[0] + s * math.cos(-math.radians(alpha5) + math.radians(alpha4))
        y = pos5[1] + s * math.sin(-math.radians(alpha5) + math.radians(alpha4))
        kwargs['x'] = x
        kwargs['y'] = y
        counter += 1

    # Add and position ipMT
    kwargs['index'] = 6

    MTname, zBeginPos, zEndPos, l = PositionComponents.assign_ipMTs_left(pos, i, **kwargs)
    pos = (x, y, zBeginPos, zEndPos, l)

    return MTname, pos, alpha, kwargs
