from pathlib import Path
from unittest import TestCase, skip

from musicscore import Chord
from musicscore.exceptions import WrongNumberOfChordsError
from musicscore.midi import C

from musicscore.score import Score
from musicscore.tests.util import diff_xml, _create_expected_path, create_test_objects
from musicscore.util import lcm, isinstance_as_string, XML_DYNAMIC_CLASSES, XML_ARTICULATION_CLASSES, \
    XML_ORNAMENT_CLASSES, XML_ORNAMENT_AND_OTHER_NOTATIONS, XML_TECHNICAL_CLASSES, XML_OTHER_NOTATIONS, \
    XML_DIRECTION_TYPE_CLASSES, XML_OTHER_NOTATIONS, XML_DIRECTION_TYPE_AND_OTHER_NOTATIONS, slur_chords, wedge_chords, \
    trill_chords, bracket_chords, octave_chords
from musicxml import XMLTrillMark, XMLWavyLine, XMLOctaveShift


class TestUtils(TestCase):
    def test_isinstance_as_string(self):
        assert isinstance_as_string(Score(), 'Score')
        assert isinstance_as_string(Score(), 'MusicTree')
        assert not isinstance_as_string(Score(), 'str')

        class Measure:
            pass

        assert not isinstance_as_string(Measure(), 'MusicTree')

        assert isinstance_as_string(C(4), 'MidiNote')
        assert isinstance_as_string(C(4), 'Midi')
        assert isinstance_as_string(C(4), 'C')
        assert not isinstance_as_string(C(4), 'str')

    def test_create_expected_path(self):
        path = Path(__file__).parent / 'test_util_diff_xml.xml'
        expected_path = _create_expected_path(path)
        assert expected_path == Path(__file__).parent / 'test_util_diff_xml_expected.xml'

    def test_diff_xml_no_diff(self):
        path = Path(__file__).parent / 'test_util_diff_xml.xml'
        assert diff_xml(path) == []

    def test_diff_xml_with_diff(self):
        path = Path(__file__).parent / 'test_util_diff_xml.xml'
        path2 = Path(__file__).parent / 'test_util_diff_xml_with_diff.xml'
        assert diff_xml(path, path2) == ['- <key>', '- <fifths>0</fifths>', '- </key>']

    def test_lcm(self):
        assert lcm([3, 4, 5, 7]) == 420
        assert lcm([2, 4, 6]) == 12

    def test_slur_chords(self):
        chords = [Chord(60, 1)]
        with self.assertRaises(WrongNumberOfChordsError):
            slur_chords(chords)
        chords.extend([Chord(61, 1), Chord(62, 1)])
        slur_chords(chords)
        assert chords[0].get_slurs()[0].type == 'start'
        assert chords[1].get_slurs()[0].type == 'continue'
        assert chords[2].get_slurs()[0].type == 'stop'

    def test_wedge_chords(self):
        chords = [Chord(60, 1)]
        with self.assertRaises(WrongNumberOfChordsError):
            wedge_chords(chords, 'crescendo')
        chords.extend([Chord(61, 1), Chord(62, 1)])
        wedge_chords(chords, 'crescendo')
        assert chords[0].get_wedges()[0].type == 'crescendo'
        assert chords[1].get_wedges()[0].type == 'continue'
        assert chords[2].get_wedges()[0].type == 'stop'

    def test_trill_chords(self):
        chords = [Chord(60, 1)]
        with self.assertRaises(WrongNumberOfChordsError):
            trill_chords(chords)
        chords.extend([Chord(61, 1), Chord(62, 1)])
        trill_chords(chords)
        assert len(chords[0].get_x(XMLTrillMark)) == 1
        assert len(chords[1].get_x(XMLTrillMark)) == 0
        assert len(chords[2].get_x(XMLTrillMark)) == 0
        assert chords[0].get_x(XMLWavyLine)[0].type == 'start'
        assert chords[1].get_x(XMLWavyLine)[0].type == 'continue'
        assert chords[2].get_x(XMLWavyLine)[0].type == 'stop'

    def test_bracket_chords(self):
        chords = [Chord(60, 1)]
        with self.assertRaises(WrongNumberOfChordsError):
            bracket_chords(chords)
        chords.extend([Chord(61, 1), Chord(62, 1)])
        bracket_chords(chords)
        assert chords[0].get_brackets()[0].type == 'start'
        assert chords[1].get_brackets()[0].type == 'continue'
        assert chords[2].get_brackets()[0].type == 'stop'

        for i, ch in enumerate(chords):
            bracket = ch.get_brackets()[0]
            assert bracket.line_type == 'solid'
            # assert bracket.placement == 'above'
            assert bracket.number == 1
            if i == 0:
                assert bracket.line_end == 'down'
            elif i == 1:
                assert bracket.line_end == 'none'
            else:
                assert bracket.line_end == 'down'

        chords = [Chord(60, 1) for _ in range(3)]
        bracket_chords(chords, 'dashed', 'none', 'up', placement='below', number=2)
        for i, ch in enumerate(chords):
            bracket = ch.get_brackets()[0]
            # assert bracket.placement == 'below'
            assert bracket.number == 2
            assert bracket.line_type == 'dashed'
            if i == 0:
                assert bracket.line_end == 'none'
            elif i == 1:
                assert bracket.line_end == 'none'
            else:
                assert bracket.line_end == 'up'

    def test_octave_chords(self):
        chords = [Chord(60, 1) for _ in range(3)]
        octave_chords(chords)
        assert chords[0].get_x(XMLOctaveShift)[0].type == 'down'
        assert chords[1].get_x(XMLOctaveShift)[0].type == 'continue'
        assert chords[2].get_x(XMLOctaveShift)[0].type == 'stop'

        chords = [Chord(60, 1) for _ in range(3)]
        octave_chords(chords, type='down', size=15, number=2)
        assert chords[0].get_x(XMLOctaveShift)[0].type == 'down'
        assert chords[1].get_x(XMLOctaveShift)[0].type == 'continue'
        assert chords[2].get_x(XMLOctaveShift)[0].type == 'stop'
        for ch in chords:
            assert ch.get_x(XMLOctaveShift)[0].size == 15
            assert ch.get_x(XMLOctaveShift)[0].number == 2


class TestTestObjects(TestCase):

    def test_direction_type_test_objects(self):
        test_object = create_test_objects(type_='direction_type')
        assert len(XML_DIRECTION_TYPE_CLASSES + XML_DIRECTION_TYPE_AND_OTHER_NOTATIONS) == len(test_object)
        for obj in test_object:
            assert obj.__class__ in XML_DIRECTION_TYPE_CLASSES or obj.__class__ in XML_DIRECTION_TYPE_AND_OTHER_NOTATIONS

    def test_ornament_test_objects(self):
        test_object = create_test_objects(type_='ornament')
        assert len(XML_ORNAMENT_CLASSES + XML_ORNAMENT_AND_OTHER_NOTATIONS) == len(test_object)
        for obj in test_object:
            assert obj.__class__ in XML_ORNAMENT_CLASSES + XML_ORNAMENT_AND_OTHER_NOTATIONS

    def test_technical_test_objects(self):
        test_object = create_test_objects(type_='technical')
        assert len(XML_TECHNICAL_CLASSES) == len(test_object)
        for obj in test_object:
            assert obj.__class__ in XML_TECHNICAL_CLASSES

    def test_articulation_test_objects(self):
        test_object = create_test_objects(type_='articulation')
        assert len(XML_ARTICULATION_CLASSES) == len(test_object)
        for obj in test_object:
            assert obj.__class__ in XML_ARTICULATION_CLASSES

    def test_other_notation_test_objects(self):
        test_object = create_test_objects(type_='notation')
        assert len(XML_OTHER_NOTATIONS + XML_ORNAMENT_AND_OTHER_NOTATIONS) == len(test_object)
        for obj in test_object:
            assert obj.__class__ in XML_OTHER_NOTATIONS + XML_ORNAMENT_AND_OTHER_NOTATIONS

    def test_dynamics_test_objects(self):
        test_object = create_test_objects(type_='dynamics')
        assert len(XML_DYNAMIC_CLASSES) == len(test_object)
        for obj in test_object:
            assert obj.__class__ in XML_DYNAMIC_CLASSES
