# -*- coding: utf-8 -*-
from abjad import Fraction
from abjad.tools import systemtools
from abjad.tools.abctools.AbjadObject import AbjadObject


class NonreducedFraction(AbjadObject, Fraction):
    r'''Nonereduced fraction.

    ..  container:: example

        Initializes with an integer numerator and integer denominator:

        ::

            >>> NonreducedFraction(3, 6)
            NonreducedFraction(3, 6)

    ..  container:: example

        Initializes with only an integer denominator:

        ::

            >>> NonreducedFraction(3)
            NonreducedFraction(3, 1)

    ..  container:: example

        Initializes with an integer pair:

        ::

            >>> NonreducedFraction((3, 6))
            NonreducedFraction(3, 6)

    ..  container:: example

        Initializes with an integer singleton:

        ::

            >>> NonreducedFraction((3,))
            NonreducedFraction(3, 1)

    ..  container:: example

        Nonreduced fractions inherit from built-in fraction:

        ::

            >>> isinstance(NonreducedFraction(3, 6), Fraction)
            True

    ..  container:: example

        Nonreduced fractions are numbers:

        ::

            >>> import numbers

        ::

            >>> isinstance(NonreducedFraction(3, 6), numbers.Number)
            True

    '''

    ### CLASS VARIABLES ###

    __slots__ = (
        '_numerator',
        '_denominator',
        )

    ### CONSTRUCTOR ###

    def __new__(class_, *arguments):
        from abjad.tools import mathtools
        if (len(arguments) == 1 and hasattr(arguments[0], 'numerator') and
            hasattr(arguments[0], 'denominator')):
            numerator = arguments[0].numerator
            denominator = arguments[0].denominator
        elif len(arguments) == 1 and isinstance(arguments[0], int):
            numerator = arguments[0]
            denominator = 1
        elif len(arguments) == 1 and mathtools.is_integer_singleton(arguments[0]):
            numerator = arguments[0][0]
            denominator = 1
        elif len(arguments) == 1 and mathtools.is_integer_pair(arguments[0]):
            numerator, denominator = arguments[0]
        elif len(arguments) == 1 and isinstance(arguments[0], str):
            numerator, denominator = class_._parse_input_string(arguments[0])
        elif mathtools.is_integer_pair(arguments):
            numerator = arguments[0]
            denominator = arguments[1]
        elif len(arguments) == 0:
            numerator = 0
            denominator = 1
        else:
            message = 'can not initialize {}: {!r}.'
            message = message.format(class_.__name__, arguments)
            raise ValueError(message)
        numerator *= mathtools.sign(denominator)
        denominator = abs(denominator)
        self = Fraction.__new__(class_, numerator, denominator)
        self._numerator = numerator
        self._denominator = denominator
        return self

    ### SPECIAL METHODS ###

    def __abs__(self):
        r'''Gets absolute value of nonreduced fraction.

        ..  container:: example

            ::

                >>> abs(NonreducedFraction(-3, 3))
                NonreducedFraction(3, 3)

        Returns nonreduced fraction.
        '''
        pair = (abs(self.numerator), self.denominator)
        return self._from_pair(pair)

    def __add__(self, argument):
        r'''Adds `argument` to nonreduced fraction.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) + 1
                NonreducedFraction(6, 3)

            ::

                >>> 1 + NonreducedFraction(3, 3)
                NonreducedFraction(6, 3)

        Returns nonreduced fraction.
        '''
        from abjad.tools import mathtools
        if isinstance(argument, int):
            numerator = self.numerator + argument * self.denominator
            pair = (numerator, self.denominator)
            return self._from_pair(pair)
        elif hasattr(argument, 'denominator'):
            if self.denominator == argument.denominator:
                numerator = self.numerator + argument.numerator
                pair = (numerator, self.denominator)
                return self._from_pair(pair)
            else:
                denominators = [self.denominator, argument.denominator]
                denominator = mathtools.least_common_multiple(*denominators)
                self_multiplier = denominator // self.denominator
                argument_multiplier = denominator // argument.denominator
                self_numerator = self_multiplier * self.numerator
                argument_numerator = argument_multiplier * argument.numerator
                pair = (self_numerator + argument_numerator, denominator)
                return self._from_pair(pair)
        else:
            raise ValueError(argument)

    def __div__(self, argument):
        r'''Divides nonreduced fraction by `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) / 1
                NonreducedFraction(3, 3)

        Returns nonreduced fraction.
        '''
        denominators = [self.denominator]
        if isinstance(argument, type(self)):
            denominators.append(argument.denominator)
            argument = argument.reduce()
        fraction = self.reduce() / argument
        return self._fraction_with_denominator(fraction, max(denominators))

    def __eq__(self, argument):
        r'''Is true when `argument` equals nonreduced fraction.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) == 1
                True

        Returns true or false.
        '''
        return self.reduce() == argument

    def __format__(self, format_specification=''):
        r'''Formats nonreduced fraction.

        Set `format_specification` to `''` or `'storage'`.
        Interprets `''` equal to `'storage'`.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(-6, 3)
                >>> print(format(fraction))
                mathtools.NonreducedFraction(-6, 3)

        Returns string.
        '''
        if format_specification in ('', 'storage'):
            return systemtools.StorageFormatAgent(self).get_storage_format()
        return str(self)

    def __ge__(self, argument):
        r'''Is true when nonreduced fraction is greater than or equal to
        `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) >= 1
                True

        Returns true or false.
        '''
        return self.reduce() >= argument

    def __gt__(self, argument):
        r'''Is true when nonreduced fraction is greater than `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) > 1
                False

        Returns true or false.
        '''
        return self.reduce() > argument

    def __hash__(self):
        r'''Hashes nonreduced fraction.

        Required to be explicitly redefined on Python 3 if __eq__ changes.

        Returns integer.
        '''
        superclass = super(NonreducedFraction, self)
        return superclass.__hash__()

    def __le__(self, argument):
        r'''Is true when nonreduced fraction is less than or equal to `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) <= 1
                True

        Returns true or false.
        '''
        return self.reduce() <= argument

    def __lt__(self, argument):
        r'''Is true when nonreduced fraction is less than `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) < 1
                False

        Returns true or false.
        '''
        return self.reduce() < argument

    def __mul__(self, argument):
        r'''Multiplies nonreduced fraction by `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) * 3
                NonreducedFraction(9, 3)

        Returns nonreduced fraction.
        '''
        denominators = [self.denominator]
        if isinstance(argument, type(self)):
            denominators.append(argument.denominator)
            argument = argument.reduce()
        fraction = self.reduce() * argument
        return self._fraction_with_denominator(fraction, max(denominators))

    def __ne__(self, argument):
        r'''Is true when `argument` does not equal nonreduced fraction.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) != 'foo'
                True

        Returns true or false.
        '''
        return not self == argument

    def __neg__(self):
        r'''Negates nonreduced fraction.

        ..  container:: example

            ::

                >>> -NonreducedFraction(3, 3)
                NonreducedFraction(-3, 3)

        Returns nonreduced fraction.
        '''
        pair = (-self.numerator, self.denominator)
        return self._from_pair(pair)

    def __pow__(self, argument):
        r'''Raises nonreduced fraction to `argument`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 6) ** -1
                NonreducedFraction(6, 3)

        Returns nonreduced fraction.
        '''
        if argument == -1:
            pair = (self.denominator, self.numerator)
            return self._from_pair(pair)
        superclass = super(NonreducedFraction, self)
        return superclass.__pow__(argument)

    def __radd__(self, argument):
        r'''Adds nonreduced fraction to `argument`.

        ..  container:: example

            ::

                >>> 1 + NonreducedFraction(3, 3)
                NonreducedFraction(6, 3)

        Returns nonreduced fraction.
        '''
        return self + argument

    def __rdiv__(self, argument):
        r'''Divides `argument` by nonreduced fraction.

        ..  container:: example

            ::

                >>> 1 / NonreducedFraction(3, 3)
                NonreducedFraction(3, 3)

        Returns nonreduced fraction.
        '''
        denominators = [self.denominator]
        if isinstance(argument, type(self)):
            denominators.append(argument.denominator)
            argument = argument.reduce()
        fraction = argument / self.reduce()
        return self._fraction_with_denominator(fraction, max(denominators))

    __rtruediv__ = __rdiv__

    def __repr__(self):
        r'''Gets interpreter representation of nonreduced fraction.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 6)
                NonreducedFraction(3, 6)

        Returns string.
        '''
        return systemtools.StorageFormatAgent(self).get_repr_format()

    def __rmul__(self, argument):
        r'''Multiplies `argument` by nonreduced fraction.

        ..  container:: example

            ::

                >>> 3 * NonreducedFraction(3, 3)
                NonreducedFraction(9, 3)

        Returns nonreduced fraction.
        '''
        return self * argument

    def __rsub__(self, argument):
        r'''Subtracts nonreduced fraction from `argument`.

        ..  container:: example

            ::

                >>> 1 - NonreducedFraction(3, 3)
                NonreducedFraction(0, 3)

        Returns nonreduced fraction.
        '''
        return -self + argument

    def __str__(self):
        r'''Gets string representation of nonreduced fraction.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(-6, 3)

            ::

                >>> str(fraction)
                '-6/3'

        Returns string.
        '''
        return '{}/{}'.format(self.numerator, self.denominator)

    def __sub__(self, argument):
        r'''Subtracts `argument` from nonreduced fraction.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 3) - 2
                NonreducedFraction(-3, 3)

            ::

                >>> NonreducedFraction(5, 4) - NonreducedFraction(18, 16)
                NonreducedFraction(2, 16)

            ::

                >>> NonreducedFraction(18, 16) - NonreducedFraction(5, 4)
                NonreducedFraction(-2, 16)

        Returns nonreduced fraction.
        '''
        denominators = [self.denominator]
        if isinstance(argument, type(self)):
            denominators.append(argument.denominator)
            argument = argument.reduce()
        fraction = self.reduce() - argument
        return self._fraction_with_denominator(fraction, max(denominators))

    def __truediv__(self, argument):
        r'''Divides nonreduced fraction in Python 3.

        Returns nonreduced fraction.
        '''
        return self.__div__(argument)

    ### PRIVATE METHODS ###

    def _fraction_with_denominator(self, fraction, denominator):
        from abjad.tools import mathtools
        denominators = [denominator, fraction.denominator]
        denominator = mathtools.least_common_multiple(*denominators)
        result = self._from_pair(fraction)
        result = result.with_denominator(denominator)
        return result

    def _from_pair(self, pair):
        r'''Method is designed to be subclassed.
        '''
        return type(self)(pair)

    def _get_format_specification(self):
        return systemtools.FormatSpecification(
            client=self,
            repr_is_indented=False,
            storage_format_args_values=[
                self.numerator,
                self.denominator,
                ],
            storage_format_is_indented=False,
            storage_format_kwargs_names=[],
            )

    @staticmethod
    def _parse_input_string(string):
        if '/' in string:
            numerator, denominator = string.split('/')
            numerator = int(numerator)
            denominator = int(denominator)
        else:
            numerator = int(string)
            denominator = 1
        return numerator, denominator

    ### PUBLIC METHODS ###

    def multiply_with_cross_cancelation(self, multiplier):
        '''Multiplies nonreduced fraction by `argument` with cross-cancelation.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(4, 8)

            ::

                >>> fraction.multiply_with_cross_cancelation((2, 3))
                NonreducedFraction(4, 12)

            ::

                >>> fraction.multiply_with_cross_cancelation((4, 1))
                NonreducedFraction(4, 2)

            ::

                >>> fraction.multiply_with_cross_cancelation((3, 5))
                NonreducedFraction(12, 40)

            ::

                >>> fraction.multiply_with_cross_cancelation((6, 5))
                NonreducedFraction(12, 20)

            ::

                >>> fraction = NonreducedFraction(5, 6)
                >>> fraction.multiply_with_cross_cancelation((6, 5))
                NonreducedFraction(1, 1)

        Returns nonreduced fraction.
        '''
        from abjad.tools import durationtools
        from abjad.tools import mathtools
        multiplier = durationtools.Multiplier(multiplier)
        self_numerator_factors = mathtools.factors(self.numerator)
        multiplier_denominator_factors = mathtools.factors(
            multiplier.denominator)
        for factor in multiplier_denominator_factors[:]:
            if factor in self_numerator_factors:
                self_numerator_factors.remove(factor)
                multiplier_denominator_factors.remove(factor)
        self_denominator_factors = mathtools.factors(self.denominator)
        multiplier_numerator_factors = mathtools.factors(multiplier.numerator)
        for factor in multiplier_numerator_factors[:]:
            if factor in self_denominator_factors:
                self_denominator_factors.remove(factor)
                multiplier_numerator_factors.remove(factor)
        result_numerator_factors = self_numerator_factors + \
            multiplier_numerator_factors
        result_denominator_factors = self_denominator_factors + \
            multiplier_denominator_factors
        result_numerator = 1
        for factor in result_numerator_factors:
            result_numerator *= factor
        result_denominator = 1
        for factor in result_denominator_factors:
            result_denominator *= factor
        pair = (result_numerator, result_denominator)
        return self._from_pair(pair)

    def multiply_with_numerator_preservation(self, multiplier):
        r'''Multiplies nonreduced fraction by `multiplier` with numerator
        preservation where possible.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(9, 16)

            ::

                >>> fraction.multiply_with_numerator_preservation((2, 3))
                NonreducedFraction(9, 24)

            ::

                >>> fraction.multiply_with_numerator_preservation((1, 2))
                NonreducedFraction(9, 32)

            ::

                >>> fraction.multiply_with_numerator_preservation((5, 6))
                NonreducedFraction(45, 96)

            ::

                >>> fraction = NonreducedFraction(3, 8)

            ::

                >>> fraction.multiply_with_numerator_preservation((2, 3))
                NonreducedFraction(3, 12)

        Returns nonreduced fraction.
        '''
        from abjad.tools import durationtools
        multiplier = durationtools.Multiplier(multiplier)
        self_denominator = self.denominator
        candidate_result_denominator = self_denominator / multiplier
        if candidate_result_denominator.denominator == 1:
            pair = (self.numerator, candidate_result_denominator.numerator)
            return self._from_pair(pair)
        else:
            result_numerator = \
                self.numerator * candidate_result_denominator.denominator
            result_denominator = candidate_result_denominator.numerator
            pair = (result_numerator, result_denominator)
            return self._from_pair(pair)

    def multiply_without_reducing(self, argument):
        r'''Multiplies nonreduced fraction by `argument` without reducing.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(3, 8)

            ::

                >>> fraction.multiply_without_reducing((3, 3))
                NonreducedFraction(9, 24)

            ::

                >>> fraction = NonreducedFraction(4, 8)

            ::

                >>> fraction.multiply_without_reducing((4, 5))
                NonreducedFraction(16, 40)

            ::

                >>> fraction.multiply_without_reducing((3, 4))
                NonreducedFraction(12, 32)

        Returns nonreduced fraction.
        '''
        argument = self._from_pair(argument)
        numerator = self.numerator * argument.numerator
        denominator = self.denominator * argument.denominator
        pair = (numerator, denominator)
        return self._from_pair(pair)

    def reduce(self):
        r'''Reduces nonreduced fraction.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(-6, 3)

            ::

                >>> fraction.reduce()
                Fraction(-2, 1)

        Returns fraction.
        '''
        return Fraction(self.numerator, self.denominator)

    def with_denominator(self, denominator):
        r'''Returns new nonreduced fraction with integer `denominator`.

        ..  container:: example

            ::

                >>> NonreducedFraction(3, 6).with_denominator(12)
                NonreducedFraction(6, 12)

        ..  container:: example

            ::

                >>> for numerator in range(12):
                ...     fraction = NonreducedFraction(numerator, 6)
                ...     print(fraction, fraction.with_denominator(12))
                ...
                0/6 0/12
                1/6 2/12
                2/6 4/12
                3/6 6/12
                4/6 8/12
                5/6 10/12
                6/6 12/12
                7/6 14/12
                8/6 16/12
                9/6 18/12
                10/6 20/12
                11/6 22/12

            ::

                >>> for numerator in range(12):
                ...     fraction = NonreducedFraction(numerator, 6)
                ...     print(fraction, fraction.with_denominator(8))
                ...
                0/6 0/8
                1/6 1/6
                2/6 2/6
                3/6 4/8
                4/6 4/6
                5/6 5/6
                6/6 8/8
                7/6 7/6
                8/6 8/6
                9/6 12/8
                10/6 10/6
                11/6 11/6

            ::

                >>> for numerator in range(12):
                ...     fraction = NonreducedFraction(numerator, 6)
                ...     print(fraction, fraction.with_denominator(12))
                ...
                0/6 0/12
                1/6 2/12
                2/6 4/12
                3/6 6/12
                4/6 8/12
                5/6 10/12
                6/6 12/12
                7/6 14/12
                8/6 16/12
                9/6 18/12
                10/6 20/12
                11/6 22/12

        Returns nonreduced fraction.
        '''
        from abjad.tools import durationtools
        current_numerator, current_denominator = self.pair
        multiplier = durationtools.Multiplier(denominator, current_denominator)
        new_numerator = multiplier * current_numerator
        new_denominator = multiplier * current_denominator
        if (new_numerator.denominator == 1 and
            new_denominator.denominator == 1):
            pair = (new_numerator.numerator, new_denominator.numerator)
        else:
            pair = (current_numerator, current_denominator)
        return self._from_pair(pair)

    def with_multiple_of_denominator(self, denominator):
        r'''Returns new nonreduced fraction with multiple of integer
        `denominator`.

        ..  container:: example

            ::

                >>> fraction = NonreducedFraction(3, 6)

            ::

                >>> fraction.with_multiple_of_denominator(5)
                NonreducedFraction(5, 10)

        ..  container:: example

            ::

                >>> NonreducedFraction(1, 2).with_multiple_of_denominator(2)
                NonreducedFraction(1, 2)

            ::

                >>> NonreducedFraction(1, 2).with_multiple_of_denominator(4)
                NonreducedFraction(2, 4)

            ::

                >>> NonreducedFraction(1, 2).with_multiple_of_denominator(8)
                NonreducedFraction(4, 8)

            ::

                >>> NonreducedFraction(1, 2).with_multiple_of_denominator(16)
                NonreducedFraction(8, 16)

        Returns nonreduced fraction.
        '''
        result = self.with_denominator(denominator)
        while not result.denominator == denominator:
            denominator *= 2
            result = result.with_denominator(denominator)
        return result

    ### PUBLIC PROPERTIES ###

    @property
    def denominator(self):
        r'''Denominator of nonreduced fraction.

        ::

            >>> fraction = mathtools.NonreducedFraction(-6, 3)

        ::

            >>> fraction.denominator
            3

        Returns positive integer.
        '''
        return self._denominator

    @property
    def imag(self):
        r'''Nonreduced fractions have no imaginary part.

        ::

            >>> fraction.imag
            0

        Returns zero.
        '''
        return 0

    @property
    def numerator(self):
        r'''Numerator of nonreduced fraction.

        ::

            >>> fraction = mathtools.NonreducedFraction(-6, 3)

        ::

            >>> fraction.numerator
            -6

        Returns integer.
        '''
        return self._numerator

    @property
    def pair(self):
        r'''Read only pair of nonreduced fraction numerator and denominator.

        ::

            >>> fraction = mathtools.NonreducedFraction(-6, 3)

        ::

            >>> fraction.pair
            (-6, 3)

        Returns integer pair.
        '''
        return self.numerator, self.denominator

    @property
    def real(self):
        r'''Nonreduced fractions are their own real component.

        ::

            >>> fraction.real
            NonreducedFraction(-6, 3)

        Returns nonreduced fraction.
        '''
        return self
