# ------------------------------------------------------------------------------
# Name:         transposition.py
# Purpose:      Tools for checking distinct transposition
#
# Authors:      Mark Gotham
#
# Copyright:    Copyright © 2017 Michael Scott Asato Cuthbert
# License:      BSD, see license.txt
# ------------------------------------------------------------------------------
from __future__ import annotations

from collections.abc import Iterable
import unittest

from music21 import chord
from music21 import common
from music21 import environment
from music21 import pitch

environLocal = environment.Environment('analysis.transposition')


class TranspositionChecker:
    '''
    Given a list of pitches, checks for the number of distinct transpositions.

    >>> pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
    >>> tc = analysis.transposition.TranspositionChecker(pList)
    >>> tc.numDistinctTranspositions()
    4
    >>> allNormalOrderPitchTuples = tc.getPitchesOfDistinctTranspositions()
    >>> allNormalOrderPitchTuples
    [(<music21.pitch.Pitch C>, <music21.pitch.Pitch E>,
                                         <music21.pitch.Pitch G#>),
     (<music21.pitch.Pitch C#>, <music21.pitch.Pitch F>,
                                         <music21.pitch.Pitch A>),
     (<music21.pitch.Pitch D>, <music21.pitch.Pitch F#>,
                                         <music21.pitch.Pitch A#>),
     (<music21.pitch.Pitch E->, <music21.pitch.Pitch G>,
                                         <music21.pitch.Pitch B>)]
    >>> myChord = chord.Chord(['C', 'E-', 'F#', 'A'])
    >>> pList = myChord.pitches
    >>> tc = analysis.transposition.TranspositionChecker(pList)
    >>> allNormalOrderChords = tc.getChordsOfDistinctTranspositions()
    >>> allNormalOrderChords
    [<music21.chord.Chord C E- F# A>,
     <music21.chord.Chord C# E G A#>,
     <music21.chord.Chord D F G# B>]
    '''
    def __init__(self, pitches: Iterable[pitch.Pitch] = ()):
        if not pitches:
            raise TypeError(
                'Must have at least one element in list'
            )
        if not common.isIterable(pitches):
            raise TypeError('Must be a list or tuple')
        # p0 = pitches[0]
        # if not isinstance(p0, pitch.Pitch):
        #     raise TypeError('List must have pitch objects')
        self.pitches: Iterable[pitch.Pitch] = pitches
        self.allTranspositions: list = []
        self.allNormalOrders: list = []
        self.distinctNormalOrders: list = []

    def getTranspositions(self):
        # noinspection PyShadowingNames
        '''
        Gets all 12 transpositions (distinct or otherwise).

        >>> p = [pitch.Pitch('D#')]
        >>> tc = analysis.transposition.TranspositionChecker(p)
        >>> tc.getTranspositions()
        [[<music21.pitch.Pitch E->],
        [<music21.pitch.Pitch E>],
        [<music21.pitch.Pitch F>],
        [<music21.pitch.Pitch F#>],
        [<music21.pitch.Pitch G>],
        [<music21.pitch.Pitch G#>],
        [<music21.pitch.Pitch A>],
        [<music21.pitch.Pitch B->],
        [<music21.pitch.Pitch B>],
        [<music21.pitch.Pitch C>],
        [<music21.pitch.Pitch C#>],
        [<music21.pitch.Pitch D>]]
        '''
        allTranspositions = []
        for i in range(12):
            thisTransposition = []
            for p in self.pitches:
                thisTransposition.append(p.transpose(i))
            allTranspositions.append(thisTransposition)
        self.allTranspositions = allTranspositions
        return allTranspositions

    def listNormalOrders(self):
        '''
        List the normal orders for all 12 transpositions

        >>> pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        >>> tc = analysis.transposition.TranspositionChecker(pList)
        >>> tc.listNormalOrders()
        [[0, 4, 8], [1, 5, 9], [2, 6, 10], [3, 7, 11],
         [0, 4, 8], [1, 5, 9], [2, 6, 10], [3, 7, 11],
         [0, 4, 8], [1, 5, 9], [2, 6, 10], [3, 7, 11]]
        '''
        if not self.allTranspositions:
            self.getTranspositions()
        allTranspositions = self.allTranspositions
        allNormalOrders = []
        for thisTransposition in allTranspositions:
            # pass
            c = chord.Chord(thisTransposition)
            thisNormalOrder = c.normalOrder
            allNormalOrders.append(thisNormalOrder)
        self.allNormalOrders = allNormalOrders
        return allNormalOrders

    def listDistinctNormalOrders(self):
        '''
        List the distinct normal orders (without duplication).

        >>> pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        >>> tc = analysis.transposition.TranspositionChecker(pList)
        >>> tc.listDistinctNormalOrders()
        [[0, 4, 8], [1, 5, 9], [2, 6, 10], [3, 7, 11]]
        '''
        if not self.allNormalOrders:
            self.listNormalOrders()
        allNormalOrders = self.allNormalOrders
        seen = set()
        distinctNormalOrders = [x for x in allNormalOrders
                                if not (tuple(x) in seen or seen.add(tuple(x)))]
        self.distinctNormalOrders = distinctNormalOrders
        return distinctNormalOrders

    def numDistinctTranspositions(self):
        '''
        Gives the number of distinct transpositions (normal orders).

        >>> pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        >>> tc = analysis.transposition.TranspositionChecker(pList)
        >>> tc.numDistinctTranspositions()
        4
        '''
        if not self.distinctNormalOrders:
            self.listDistinctNormalOrders()
        return len(self.distinctNormalOrders)

    def getChordsOfDistinctTranspositions(self):
        '''
        Outputs chords for each distinct transposition (normal order).

        >>> pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        >>> tc = analysis.transposition.TranspositionChecker(pList)
        >>> tc.getChordsOfDistinctTranspositions()
        [<music21.chord.Chord C E G#>,
         <music21.chord.Chord C# F A>,
         <music21.chord.Chord D F# A#>,
         <music21.chord.Chord E- G B>]
        '''
        if not self.distinctNormalOrders:
            self.listDistinctNormalOrders()
        distinctNormalOrders = self.distinctNormalOrders
        allNormalOrderChords = []
        for thisNormalOrder in distinctNormalOrders:
            thisNormalOrderChord = chord.Chord(thisNormalOrder)
            allNormalOrderChords.append(thisNormalOrderChord)
        return allNormalOrderChords

    def getPitchesOfDistinctTranspositions(self):
        '''
        Outputs pitch tuples for each distinct transposition (normal order).

        >>> pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        >>> tc = analysis.transposition.TranspositionChecker(pList)
        >>> tc.getPitchesOfDistinctTranspositions()
        [(<music21.pitch.Pitch C>, <music21.pitch.Pitch E>, <music21.pitch.Pitch G#>),
         (<music21.pitch.Pitch C#>, <music21.pitch.Pitch F>, <music21.pitch.Pitch A>),
         (<music21.pitch.Pitch D>, <music21.pitch.Pitch F#>, <music21.pitch.Pitch A#>),
         (<music21.pitch.Pitch E->, <music21.pitch.Pitch G>, <music21.pitch.Pitch B>)]
        '''
        chords = self.getChordsOfDistinctTranspositions()
        allNormalOrderPitchTuples = [c.pitches for c in chords]
        return allNormalOrderPitchTuples

# ------------------------------------------------------------------------------
class Test(unittest.TestCase):
    def testCopyAndDeepcopy(self):
        from music21.test.commonTest import testCopyAll
        testCopyAll(self, globals())

    def testConstructTranspositionChecker(self):
        p = [pitch.Pitch('D#')]
        tc = TranspositionChecker(p)

        self.assertEqual(tc.pitches, p)
        numberOfPitchesInTc = len(tc.pitches)
        self.assertEqual(numberOfPitchesInTc, len(p))

    def testTranspositions(self):
        p = [pitch.Pitch('D#')]
        tc = TranspositionChecker(p)
        allTranspositions = tc.getTranspositions()

        self.assertEqual(len(allTranspositions), 12)
        self.assertIsInstance(allTranspositions[0][0], pitch.Pitch)
        self.assertEqual(allTranspositions[0][0].midi, p[0].midi)
        self.assertEqual(allTranspositions[1][0].midi, p[0].midi + 1)

        p = [pitch.Pitch('D#'), pitch.Pitch('F')]
        tc = TranspositionChecker(p)
        allTranspositions = tc.getTranspositions()

        self.assertEqual(len(allTranspositions), 12)
        self.assertIsInstance(allTranspositions[0][0], pitch.Pitch)
        self.assertEqual(allTranspositions[0][0].midi, p[0].midi)
        self.assertEqual(allTranspositions[0][1].midi, p[1].midi)

    def testNormalOrders(self):
        pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        tc = TranspositionChecker(pList)
        normalOrders = tc.listNormalOrders()

        self.assertEqual(len(normalOrders), 12)
        self.assertLess(normalOrders[0][0], 13)

    def testDistinctNormalOrders(self):
        pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        tc = TranspositionChecker(pList)
        allDistinctNormalOrders = tc.listDistinctNormalOrders()

        lengthDistinctNormalOrders = tc.numDistinctTranspositions()

        self.assertEqual(len(allDistinctNormalOrders), 4)
        self.assertEqual(lengthDistinctNormalOrders, 4)
        self.assertIsInstance(allDistinctNormalOrders, list)
        self.assertEqual(allDistinctNormalOrders[0], [0, 4, 8])

    def testNormalOrderChords(self):
        pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        tc = TranspositionChecker(pList)

        allNormalOrderChords = tc.getChordsOfDistinctTranspositions()

        self.assertEqual(len(allNormalOrderChords), 4)
        # self.assertEqual(lengthDistinctNormalOrders, 4)
        self.assertIsInstance(allNormalOrderChords[0], chord.Chord)
        self.assertIsInstance(allNormalOrderChords[0].pitches[0], pitch.Pitch)
        # self.assertEqual(allDistinctNormalOrders[0], [0,4,8])

    def testNormalOrdersPitches(self):
        pList = [pitch.Pitch('C4'), pitch.Pitch('E4'), pitch.Pitch('G#4')]
        tc = TranspositionChecker(pList)

        allNormalOrderPitchTuples = tc.getPitchesOfDistinctTranspositions()

        self.assertEqual(len(allNormalOrderPitchTuples), 4)
        # self.assertEqual(lengthDistinctNormalOrders, 4)
        self.assertIsInstance(allNormalOrderPitchTuples[0], tuple)
        self.assertIsInstance(allNormalOrderPitchTuples[0][0], pitch.Pitch)
        # self.assertEqual(allDistinctNormalOrders[0], [0,4,8])


# -----------------------------------------------------------------------------
if __name__ == '__main__':
    import music21
    music21.mainTest(Test)
