# Solfege - free ear training software
# Copyright (C) 2007, 2008 Tom Cato Amundsen
# License is GPL, see file COPYING

import unittest
from solfege.mpd.parser import Clef, UnknownClefException, parse_to_score_object, ParseError, validate_only_notenames
from solfege import mpd

class TestClef(unittest.TestCase):
    def test_constructor_ok(self):
        c = Clef("violin")
        self.assertEquals(c.m_octaviation, 0)
        self.assertEquals(c.m_name, "violin")
        c = Clef("violin_8")
        self.assertEquals(c.m_octaviation, -7)
    def test_constructor_unknown_clef(self):
        self.assertRaises(UnknownClefException, lambda : Clef("not a clefname"))
        self.assertRaises(UnknownClefException, lambda : Clef("violin _ 8"))
    def test_octaviation(self):
        self.assertEquals(Clef("violin").steps_to_ylinepos(0), 13)
        self.assertEquals(Clef("violin_8").steps_to_ylinepos(0), 13 - 7)
    def test_steps_to_ylinepos(self):
        for s, c, o, i in (
            ("violin", "violin", 0, 13),
            ("violin_8", "violin", -7, 13 - 7),
            ("violin_15", "violin", -14, 13 - 14),
            ("violin^8", "violin", 7, 13 + 7),
            ("violin^15", "violin", 14, 13 + 14),
            ):
            clef = Clef(s)
            self.assertEquals(clef.m_name, c)
            self.assertEquals(clef.m_octaviation, o)
            self.assertEquals(clef.steps_to_ylinepos(0), i)

class TestMpdParser(unittest.TestCase):
    """
    These tests does not check that the engraving is correct,
    but we at least know that the parser and engraver does not
    crash.
    """
    def _check(self, e, s, substring):
        """
        e is an exception.
        s is the string to search in
        substring is the string to find
        """
        self.assertEquals(s.split("\n")[e.m_lineno]
                                       [e.m_linepos1:e.m_linepos2], substring)
    def test_rest(self):
        score = parse_to_score_object(r"\staff {r}")
        eng = score.get_engravers(20)
    def test_dotted_rest(self):
        score = parse_to_score_object(r"\staff {r4.}")
        eng = score.get_engravers(20)
        score = parse_to_score_object(r"\staff {r4..}")
        eng = score.get_engravers(20)
    def test_dottt(self):
        t = mpd.music_to_tracklist(r"\staff { c4 r4. c4 }")
        self.assertEquals(t[0].str_repr(), "n48 d1/4 o48 d3/8 n48 d1/4 o48")
        t = mpd.music_to_tracklist(r"\staff { c4 r4.. c4 }")
        self.assertEquals(t[0].str_repr(), "n48 d1/4 o48 d7/16 n48 d1/4 o48")
        t = mpd.music_to_tracklist(r"\staff { c4 r4. r4.. c4 }")
        self.assertEquals(t[0].str_repr(), "n48 d1/4 o48 d13/16 n48 d1/4 o48")
    def test_parse_brace_not_allowed(self):
        s = "\\staff{ c4 { e \n" \
            "}"
        try:
            t = mpd.music_to_tracklist(s)
        except ParseError, e:
            self._check(e, s, '{')
            self.assertEquals((e.m_linepos1, e.m_linepos2), (11, 12))
        else:
            self.assertFalse("No exception raised!")
    def test_parse_err1(self):
        s = "\\staff{ c4 < d < e \n" \
            "}"
        try:
            t = mpd.music_to_tracklist(s)
        except ParseError, e:
            self._check(e, s, '<')
            self.assertEquals((e.m_linepos1, e.m_linepos2), (15, 16))
        else:
            self.assertFalse("No exception raised!")
    def test_parse_err2(self):
        s = "\\staff{ \n" \
            "  c4 < d < e \n" \
            "}"
        try:
            t = mpd.music_to_tracklist(s)
        except ParseError, e:
            self._check(e, s, '<')
            self.assertEquals((e.m_linepos1, e.m_linepos2), (9, 10))
        else:
            self.assertFalse("No exception raised!")
    def test_parse_err3(self):
        s = "\\staff{ \n" \
            "  c4 d > \times 3/2"
        try:
            t = mpd.music_to_tracklist(s)
        except ParseError, e:
            self._check(e, s, '>')
            self.assertEquals((e.m_linepos1, e.m_linepos2), (7, 8))
        else:
            self.assertFalse("No exception raised!")
    def test_parse_times_do_not_nest(self):
        s = "\\staff { c4 \\times 3/2 { \\times 3/2 { c4 c4 } } }\n"
        try:
            t = mpd.music_to_tracklist(s)
        except ParseError, e:
            self._check(e, s, '\\times 3/2 {')
            self.assertEquals((e.m_linepos1, e.m_linepos2), (25, 37))
        else:
            self.assertFalse("No exception raised!")
    def test_parse_addvoice_before_staff(self):
        s = "   \\addvoice { \n" \
            "  c4 d }"
        try:
            t = mpd.music_to_tracklist(s)
        except ParseError, e:
            self._check(e, s, '\\addvoice')
            self.assertEquals((e.m_linepos1, e.m_linepos2), (3, 12))
        else:
            self.assertFalse("No exception raised!")
    def test_whitespace_at_end(self):
        s = "   \\staff { \n" \
            "  c4 d } \n" \
            "\t \n"
        t = mpd.music_to_tracklist(s)

class TestLexer(unittest.TestCase):
    def test_simplest(self):
        l = mpd.parser.Lexer("c4 r4")
        g = l.next()
        self.assertEquals(l.NOTE, g[0])
        self.assertEquals(g[1].m_duration.get_rat_value(), mpd.Rat(1, 4))
        g = l.next()
        self.assertEquals(l.REST, g[0])
        self.assertEquals(g[1].m_duration.get_rat_value(), mpd.Rat(1, 4))
    def test_note_dotting(self):
        l = mpd.parser.Lexer("c4 c4. c.")
        self.assertEquals(l.next()[1].m_duration, mpd.duration.Duration(4, 0))
        t, m = l.next()
        self.assertEquals(t, mpd.parser.Lexer.NOTE)
        self.assertEquals(m.m_duration, mpd.duration.Duration(4, 1))
        self.assertRaises(mpd.parser.LexerError, lambda : l.next())
    def test_rest_dotting(self):
        l = mpd.parser.Lexer("c4 r4. r.")
        self.assertEquals(l.next()[1].m_duration, mpd.duration.Duration(4, 0))
        t, m = l.next()
        self.assertEquals(t, mpd.parser.Lexer.REST)
        self.assertEquals(m.m_duration, mpd.duration.Duration(4, 1))
        self.assertRaises(mpd.parser.LexerError, lambda : l.next())
    def test_inherited_time_dotting(self):
        l = mpd.parser.Lexer("r4.. c")
        t, m = l.next()
        self.assertEquals(t, mpd.parser.Lexer.REST)
        self.assertEquals(m.m_duration, mpd.duration.Duration(4, 2))
        t, m = l.next()
        self.assertEquals(t, mpd.parser.Lexer.NOTE)
        self.assertEquals(getattr(m, 'm_duration', None), None)
    def test_whitespace(self):
        l = mpd.parser.Lexer("\t\nc4 \n r4   \n")
        g = l.next()
        self.assertEquals(l.NOTE, g[0])
        self.assertEquals(g[1].m_duration.get_rat_value(), mpd.Rat(1, 4))
        g = l.next()
        self.assertEquals(l.REST, g[0])
        self.assertEquals(g[1].m_duration.get_rat_value(), mpd.Rat(1, 4))
    def test_tokenizing(self):
        for music in (
                r"c d e",
                r"c4 d e8",
                r"c4 d8. e f4",
                r"\staff{ c }",
                r"\staff{ \time 3/4 c'8 g, }",
                r"\staff{ \time 3/4 c'8 g, } \addvoice{ c }",
                r"\staff{ \clef bass c }",
                r"\stemUp c \stemDown d \stemBoth e",
                r"\staff\transpose d'{ c }",
                r"\key a \minor c \key fis \major",
                r"c4 s2 s s8",
                r"r r2 r",
                r"\staff\relative g''{ c d e }",
                r"\times 2/3{ c }",
                r"\staff{ \tupletDown \times 2/3{ [ c'8 d' e' ] } }",
                r"c \tupletDown d \tupletBoth e \tupletUp f",
                r"< c4 e g >",
                r"[ c'8 e ]",
                ):
            self.assertEquals(music, mpd.parser.Lexer.to_string(list(mpd.parser.Lexer(music))))
    def test_set_first_pitch_ok(self):
        fis = mpd.MusicalPitch.new_from_notename("fis")
        for s, n in (
            ("c d e", "fis d e"),
            ("<c'' e>", "< fis e>"),
            ("< c'' e>", "< fis e>"),
            (" c,,16", "fis16"),
            (" \n c d e", "fis d e"),
            (r"\clef bass c d e", r"\clef bass fis d e"),
            (r"\clef violin \time 3/8 g", r"\clef violin \time 3/8 fis"),
            (r'\clef "violin_8" d', r'\clef "violin_8" fis'),
            ("\\key g \\major \\time 2/4\n d'8 | [g g]", 
             "\\key g \\major \\time 2/4 fis8 | [g g]"),
            ("\nc", "fis"),
            ("", ""),
            ):
            lex = mpd.parser.Lexer(s)
            lex.set_first_pitch(fis)
            self.assertEquals(lex.m_string, n)
    def test_set_first_pitch_errors(self):
        fis = mpd.MusicalPitch.new_from_notename("fis")
        lex = mpd.parser.Lexer("c d ERR e f")
        lex.set_first_pitch(fis)
        self.assertEquals(lex.m_string, "fis d ERR e f")
        #
        lex = mpd.parser.Lexer("\clef bass c d ERR e f")
        lex.set_first_pitch(fis)
        self.assertEquals(lex.m_string, "\clef bass fis d ERR e f")
        #
        lex = mpd.parser.Lexer("\clef bass c d ERR e f")
        lex.set_first_pitch(fis)
        self.assertEquals(lex.m_string, "\clef bass fis d ERR e f")
    def test_m_notelen(self):
        lexer = mpd.parser.Lexer("c8 d s16 r32")
        toc, toc_data = lexer.next()
        self.assertEquals(toc_data.m_duration.get_rat_value(), mpd.Rat(1, 8))
        self.assertEquals(lexer.m_notelen.get_rat_value(), mpd.Rat(1, 8))
        toc, toc_data = lexer.next()
        self.assertEquals(lexer.m_notelen.get_rat_value(), mpd.Rat(1, 8))
        toc, toc_data = lexer.next()
        self.assertEquals(toc_data.m_duration.get_rat_value(), mpd.Rat(1, 16))
        toc, toc_data = lexer.next()
        self.assertEquals(toc_data.m_duration.get_rat_value(), mpd.Rat(1, 32))


class TestFunctions(unittest.TestCase):
    def test_validate_only_notenames(self):
        self.assertEquals(validate_only_notenames("c e g"), (None, None, None))
        self.assertEquals(validate_only_notenames("c4 e2 g"), (None, None, None))
        self.assertEquals(validate_only_notenames("c4 e2 g~ g"), (0, 7, 8))
        self.assertEquals(validate_only_notenames("c4 [e8 e] "), (0, 3, 4))
        self.assertEquals(validate_only_notenames("c ERR g"), (0, 2, 5))
        self.assertEquals(validate_only_notenames("c\nERR\ng"), (1, 0, 3))
        self.assertEquals(validate_only_notenames("c\n  ERR\ng"), (1, 2, 5))


suite = unittest.makeSuite(TestClef)
suite.addTest(unittest.makeSuite(TestMpdParser))
suite.addTest(unittest.makeSuite(TestFunctions))
suite.addTest(unittest.makeSuite(TestLexer))
