# -*- coding: utf-8 -*-
"""Tests for sgf.py."""

from __future__ import with_statement

from textwrap import dedent

from gomill_tests import gomill_test_support

from gomill import sgf

def make_tests(suite):
    suite.addTests(gomill_test_support.make_simple_tests(globals()))


def test_new_sgf_game(tc):
    g1 = sgf.Sgf_game(9)
    tc.assertEqual(g1.get_size(), 9)
    root = g1.get_root()
    tc.assertEqual(root.get_raw('FF'), '4')
    tc.assertEqual(root.get_raw('GM'), '1')
    tc.assertEqual(root.get_raw('SZ'), '9')
    tc.assertEqual(root.get_raw_property_map(), {
        'FF': ['4'],
        'GM': ['1'],
        'SZ': ['9'],
        'CA': ['UTF-8'],
        });
    tc.assertEqual(list(root), [])
    tc.assertEqual(root.parent, None)
    tc.assertIs(root.owner, g1)

def test_sgf_game_from_coarse_game_tree(tc):
    class Namespace(object):
        pass
    coarse_game = Namespace()
    coarse_game.sequence = [{'SZ' : ["9"]}, {'B' : ["aa"]}]
    coarse_game.children = []
    g1 = sgf.Sgf_game.from_coarse_game_tree(coarse_game)
    tc.assertEqual(g1.get_size(), 9)
    root = g1.get_root()
    tc.assertIs(root.get_raw_property_map(), coarse_game.sequence[0])
    tc.assertEqual(root.parent, None)
    tc.assertIs(root.owner, g1)
    tc.assertEqual(len(root), 1)

    coarse_game2 = Namespace()
    coarse_game2.sequence = [{'SZ' : ["0"]}, {'B' : ["aa"]}]
    coarse_game2.children = []
    tc.assertRaisesRegexp(ValueError, "size out of range: 0",
                          sgf.Sgf_game.from_coarse_game_tree, coarse_game2)

def test_sgf_game_from_string(tc):
    g1 = sgf.Sgf_game.from_string("(;)")
    tc.assertEqual(g1.get_size(), 19)
    tc.assertRaisesRegexp(ValueError, "unexpected end of SGF data",
                          sgf.Sgf_game.from_string, "(;SZ[9]")
    g2 = sgf.Sgf_game.from_string("(;SZ[9])")
    tc.assertEqual(g2.get_size(), 9)
    tc.assertRaisesRegexp(ValueError, "bad SZ property: a",
                          sgf.Sgf_game.from_string, "(;SZ[a])")
    tc.assertRaisesRegexp(ValueError, "size out of range: 27",
                          sgf.Sgf_game.from_string, "(;SZ[27])")
    tc.assertRaisesRegexp(ValueError, "unknown encoding: $",
                          sgf.Sgf_game.from_string, "(;CA[])")

def test_node(tc):
    sgf_game = sgf.Sgf_game.from_string(
        r"(;KM[6.5]C[sample\: comment]AB[ai][bh][ee]AE[];B[dg])")
    node0 = sgf_game.get_root()
    node1 = list(sgf_game.main_sequence_iter())[1]
    tc.assertEqual(node0.get_size(), 19)
    tc.assertEqual(node0.get_encoding(), "ISO-8859-1")
    tc.assertIs(node0.has_property('KM'), True)
    tc.assertIs(node0.has_property('XX'), False)
    tc.assertIs(node1.has_property('KM'), False)
    tc.assertEqual(set(node0.properties()), set(["KM", "C", "AB", "AE"]))
    tc.assertEqual(set(node1.properties()), set(["B"]))
    tc.assertEqual(node0.get_raw('C'), r"sample\: comment")
    tc.assertEqual(node0.get_raw('AB'), "ai")
    tc.assertEqual(node0.get_raw('AE'), "")
    tc.assertRaises(KeyError, node0.get_raw, 'XX')
    tc.assertEqual(node0.get_raw_list('KM'), ['6.5'])
    tc.assertEqual(node0.get_raw_list('AB'), ['ai', 'bh', 'ee'])
    tc.assertEqual(node0.get_raw_list('AE'), [''])
    tc.assertRaises(KeyError, node0.get_raw_list, 'XX')
    tc.assertRaises(KeyError, node0.get_raw, 'XX')

def test_property_combination(tc):
    sgf_game = sgf.Sgf_game.from_string("(;XX[1]YY[2]XX[3]YY[4])")
    node0 = sgf_game.get_root()
    tc.assertEqual(node0.get_raw_list("XX"), ["1", "3"])
    tc.assertEqual(node0.get_raw_list("YY"), ["2", "4"])

def test_node_get(tc):
    sgf_game = sgf.Sgf_game.from_string(dedent(r"""
    (;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
    PL[B]PW[White engine][xs]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fd][gc]AE[]BM[2]VW[]
    EV[Test
    event]
    C[123:\)
    abc]
    YY[none
    sense]
    ;B[dg]KO[]AR[ab:cd][de:fg]FG[515:first move]
    LB[ac:lbl][bc:lbl2])
    """))
    root = sgf_game.get_root()
    node1 = list(sgf_game.main_sequence_iter())[1]
    tc.assertRaises(KeyError, root.get, 'XX')
    tc.assertEqual(root.get('C'), "123:)\nabc")          # Text
    tc.assertEqual(root.get('EV'), "Test event")         # Simpletext
    tc.assertEqual(root.get('BM'), 2)                    # Double
    tc.assertEqual(root.get('YY'), "none\nsense")        # unknown (Text)
    tc.assertIs(node1.get('KO'), True)                   # None
    tc.assertEqual(root.get('KM'), 7.5)                  # Real
    tc.assertEqual(root.get('GM'), 1)                    # Number
    tc.assertEqual(root.get('PL'), 'b')                  # Color
    tc.assertEqual(node1.get('B'), (2, 3))               # Point
    tc.assertEqual(root.get('AB'),
                   set([(0, 0), (1, 1), (4, 4)]))        # List of Point
    tc.assertEqual(root.get('VW'), set())                # Empty elist
    tc.assertEqual(root.get('AP'), ("testsuite", "0"))   # Application
    tc.assertEqual(node1.get('AR'),
                   [((7, 0), (5, 2)), ((4, 3), (2, 5))]) # Arrow
    tc.assertEqual(node1.get('FG'), (515, "first move")) # Figure
    tc.assertEqual(node1.get('LB'),
                   [((6, 0), "lbl"), ((6, 1), "lbl2")])  # Label
    # Check we (leniently) treat lists like elists on read
    tc.assertEqual(root.get('AE'), set())
    tc.assertRaisesRegexp(ValueError, "multiple values", root.get, 'PW')

def test_text_values(tc):
    def check(s):
        sgf_game = sgf.Sgf_game.from_string(s)
        return sgf_game.get_root().get("C")
    # Round-trip check of Text values through tokeniser, parser, and
    # text_value().
    tc.assertEqual(check(r"(;C[abc]KO[])"), r"abc")
    tc.assertEqual(check(r"(;C[a\\bc]KO[])"), r"a\bc")
    tc.assertEqual(check(r"(;C[a\\bc\]KO[])"), r"a\bc]KO[")
    tc.assertEqual(check(r"(;C[abc\\]KO[])"), r"abc" + "\\")
    tc.assertEqual(check(r"(;C[abc\\\]KO[])"), r"abc\]KO[")
    tc.assertEqual(check(r"(;C[abc\\\\]KO[])"), r"abc" + "\\\\")
    tc.assertEqual(check(r"(;C[abc\\\\\]KO[])"), r"abc\\]KO[")
    tc.assertEqual(check(r"(;C[xxx :\) yyy]KO[])"), r"xxx :) yyy")
    tc.assertEqual(check("(;C[ab\\\nc])"), "abc")
    tc.assertEqual(check("(;C[ab\nc])"), "ab\nc")


SAMPLE_SGF = """\
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
PL[B]PW[White engine]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fc][gc];B[dg];W[ef]C[comment
on two lines];B[];W[tt]C[Final comment])
"""

SAMPLE_SGF_VAR = """\
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
PL[B]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fd][gc]VW[]
;B[dg]
;W[ef]C[comment
on two lines]
;B[]
;C[Nonfinal comment]VW[aa:bb]
(;B[ia];W[ib];B[ic])
(;B[ib];W[ic]
  (;B[id])
  (;B[ie])
))
"""

def test_node_string(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF)
    node = sgf_game.get_root()
    tc.assertMultiLineEqual(str(node), dedent("""\
    AB[ai][bh][ee]
    AP[testsuite:0]
    AW[fc][gc]
    CA[utf-8]
    DT[2009-06-06]
    FF[4]
    GM[1]
    KM[7.5]
    PB[Black engine]
    PL[B]
    PW[White engine]
    RE[W+R]
    SZ[9]
    """))

def test_node_get_move(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF)
    nodes = list(sgf_game.main_sequence_iter())
    tc.assertEqual(nodes[0].get_move(), (None, None))
    tc.assertEqual(nodes[1].get_move(), ('b', (2, 3)))
    tc.assertEqual(nodes[2].get_move(), ('w', (3, 4)))
    tc.assertEqual(nodes[3].get_move(), ('b', None))
    tc.assertEqual(nodes[4].get_move(), ('w', None))

def test_node_get_setup_stones(tc):
    sgf_game = sgf.Sgf_game.from_string(
        r"(;KM[6.5]SZ[9]C[sample\: comment]AB[ai][bh][ee]AE[bb];B[dg])")
    node0 = sgf_game.get_root()
    node1 = list(sgf_game.main_sequence_iter())[1]
    tc.assertIs(node0.has_setup_stones(), True)
    tc.assertIs(node1.has_setup_stones(), False)
    tc.assertEqual(node0.get_setup_stones(),
                   (set([(0, 0), (1, 1), (4, 4)]), set(), set([(7, 1)])))
    tc.assertEqual(node1.get_setup_stones(),
                   (set(), set(), set()))

def test_sgf_game(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    nodes = list(sgf_game.main_sequence_iter())
    tc.assertEqual(sgf_game.get_size(), 9)
    tc.assertEqual(sgf_game.get_komi(), 7.5)
    tc.assertIs(sgf_game.get_handicap(), None)
    tc.assertEqual(sgf_game.get_player_name('b'), "Black engine")
    tc.assertIs(sgf_game.get_player_name('w'), None)
    tc.assertEqual(sgf_game.get_winner(), 'w')
    tc.assertEqual(nodes[2].get('C'), "comment\non two lines")
    tc.assertEqual(nodes[4].get('C'), "Nonfinal comment")

    g2 = sgf.Sgf_game.from_string("(;)")
    tc.assertEqual(g2.get_size(), 19)
    tc.assertEqual(g2.get_komi(), 0.0)
    tc.assertIs(g2.get_handicap(), None)
    tc.assertIs(g2.get_player_name('b'), None)
    tc.assertIs(g2.get_player_name('w'), None)
    tc.assertEqual(g2.get_winner(), None)

def test_tree_view(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    root = sgf_game.get_root()
    tc.assertIsInstance(root, sgf.Tree_node)
    tc.assertIs(root.parent, None)
    tc.assertIs(root.owner, sgf_game)
    tc.assertEqual(len(root), 1)
    tc.assertEqual(root[0].get_raw('B'), "dg")
    tc.assertTrue(root)
    tc.assertEqual(root.index(root[0]), 0)

    branchnode = root[0][0][0][0]
    tc.assertIsInstance(branchnode, sgf.Tree_node)
    tc.assertIs(branchnode.parent, root[0][0][0])
    tc.assertIs(branchnode.owner, sgf_game)
    tc.assertEqual(len(branchnode), 2)
    tc.assertIs(branchnode[1], branchnode[-1])
    tc.assertEqual(branchnode[:1], [branchnode[0]])
    tc.assertEqual([node for node in branchnode],
                   [branchnode[0], branchnode[1]])
    with tc.assertRaises(IndexError):
        branchnode[2]
    tc.assertEqual(branchnode[0].get_raw('B'), "ia")
    tc.assertEqual(branchnode[1].get_raw('B'), "ib")
    tc.assertEqual(branchnode.index(branchnode[0]), 0)
    tc.assertEqual(branchnode.index(branchnode[1]), 1)

    tc.assertEqual(len(branchnode[1][0]), 2)

    leaf = branchnode[1][0][1]
    tc.assertIs(leaf.parent, branchnode[1][0])
    tc.assertEqual(len(leaf), 0)
    tc.assertFalse(leaf)

    tc.assertIs(sgf_game.get_last_node(), root[0][0][0][0][0][0][0])

    # check nothing breaks when first retrieval is by index
    game2 = sgf.Sgf_game.from_string(SAMPLE_SGF)
    root2 = game2.get_root()
    tc.assertEqual(root2[0].get_raw('B'), "dg")

def test_serialise(tc):
    # Doesn't cover transcoding
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    serialised = sgf_game.serialise()
    tc.assertEqual(serialised, dedent("""\
    (;FF[4]AB[ai][bh][ee]AP[testsuite:0]AW[fd][gc]CA[utf-8]DT[2009-06-06]GM[1]
    KM[7.5]PB[Black engine]PL[B]RE[W+R]SZ[9]VW[];B[dg];C[comment
    on two lines]W[ef]
    ;B[];C[Nonfinal comment]VW[aa:bb](;B[ia];W[ib];B[ic])(;B[ib];W[ic](;B[id])(;
    B[ie])))
    """))
    sgf_game2 = sgf.Sgf_game.from_string(serialised)
    tc.assertEqual(map(str, sgf_game.get_main_sequence()),
                   map(str, sgf_game2.get_main_sequence()))

def test_serialise_wrap(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    serialised = sgf_game.serialise(wrap=None)
    tc.assertEqual(serialised, dedent("""\
    (;FF[4]AB[ai][bh][ee]AP[testsuite:0]AW[fd][gc]CA[utf-8]DT[2009-06-06]GM[1]KM[7.5]PB[Black engine]PL[B]RE[W+R]SZ[9]VW[];B[dg];C[comment
    on two lines]W[ef];B[];C[Nonfinal comment]VW[aa:bb](;B[ia];W[ib];B[ic])(;B[ib];W[ic](;B[id])(;B[ie])))
    """))
    sgf_game2 = sgf.Sgf_game.from_string(serialised)
    tc.assertEqual(map(str, sgf_game.get_main_sequence()),
                   map(str, sgf_game2.get_main_sequence()))

def test_encoding(tc):
    g1 = sgf.Sgf_game(19)
    tc.assertEqual(g1.get_charset(), "UTF-8")
    root = g1.get_root()
    tc.assertEqual(root.get_encoding(), "UTF-8")
    root.set("C", "£")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "£")
    tc.assertEqual(g1.serialise(), dedent("""\
    (;FF[4]C[£]CA[UTF-8]GM[1]SZ[19])
    """))

    g2 = sgf.Sgf_game(19, encoding="iso-8859-1")
    tc.assertEqual(g2.get_charset(), "ISO-8859-1")
    root = g2.get_root()
    tc.assertEqual(root.get_encoding(), "ISO-8859-1")
    root.set("C", "£")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "\xa3")
    tc.assertEqual(g2.serialise(), dedent("""\
    (;FF[4]C[\xa3]CA[ISO-8859-1]GM[1]SZ[19])
    """))

    tc.assertRaisesRegexp(ValueError, "unknown encoding: unknownencoding",
                          sgf.Sgf_game, 19, "unknownencoding")


def test_loaded_sgf_game_encoding(tc):
    g1 = sgf.Sgf_game.from_string("""
    (;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
    """)
    tc.assertEqual(g1.get_charset(), "UTF-8")
    root = g1.get_root()
    tc.assertEqual(root.get_encoding(), "UTF-8")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "£")
    tc.assertEqual(g1.serialise(), dedent("""\
    (;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
    """))

    g2 = sgf.Sgf_game.from_string("""
    (;FF[4]C[\xa3]CA[iso-8859-1]GM[1]SZ[19])
    """)
    tc.assertEqual(g2.get_charset(), "ISO-8859-1")
    root = g2.get_root()
    tc.assertEqual(root.get_encoding(), "ISO-8859-1")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "\xa3")
    tc.assertEqual(g2.serialise(), dedent("""\
    (;FF[4]C[\xa3]CA[iso-8859-1]GM[1]SZ[19])
    """))

    g3 = sgf.Sgf_game.from_string("""
    (;FF[4]C[\xa3]GM[1]SZ[19])
    """)
    tc.assertEqual(g3.get_charset(), "ISO-8859-1")
    root = g3.get_root()
    tc.assertEqual(root.get_encoding(), "ISO-8859-1")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "\xa3")
    tc.assertEqual(g3.serialise(), dedent("""\
    (;FF[4]C[\xa3]GM[1]SZ[19])
    """))

    # This is invalidly encoded. get() notices, but serialise() doesn't care.
    g4 = sgf.Sgf_game.from_string("""
    (;FF[4]C[\xa3]CA[utf-8]GM[1]SZ[19])
    """)
    tc.assertEqual(g4.get_charset(), "UTF-8")
    root = g4.get_root()
    tc.assertEqual(root.get_encoding(), "UTF-8")
    tc.assertRaises(UnicodeDecodeError, root.get, "C")
    tc.assertEqual(root.get_raw("C"), "\xa3")
    tc.assertEqual(g4.serialise(), dedent("""\
    (;FF[4]C[\xa3]CA[utf-8]GM[1]SZ[19])
    """))

    tc.assertRaisesRegexp(
        ValueError, "unknown encoding: unknownencoding",
        sgf.Sgf_game.from_string, """
        (;FF[4]CA[unknownencoding]GM[1]SZ[19])
        """)

def test_override_encoding(tc):
    g1 = sgf.Sgf_game.from_string("""
    (;FF[4]C[£]CA[iso-8859-1]GM[1]SZ[19])
    """, override_encoding="utf-8")
    root = g1.get_root()
    tc.assertEqual(root.get_encoding(), "UTF-8")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "£")
    tc.assertEqual(g1.serialise(), dedent("""\
    (;FF[4]C[£]CA[UTF-8]GM[1]SZ[19])
    """))

    g2 = sgf.Sgf_game.from_string("""
    (;FF[4]C[\xa3]CA[utf-8]GM[1]SZ[19])
    """, override_encoding="iso-8859-1")
    root = g2.get_root()
    tc.assertEqual(root.get_encoding(), "ISO-8859-1")
    tc.assertEqual(root.get("C"), "£")
    tc.assertEqual(root.get_raw("C"), "\xa3")
    tc.assertEqual(g2.serialise(), dedent("""\
    (;FF[4]C[\xa3]CA[ISO-8859-1]GM[1]SZ[19])
    """))

def test_serialise_transcoding(tc):
    g1 = sgf.Sgf_game.from_string("""
    (;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
    """)
    tc.assertEqual(g1.serialise(), dedent("""\
    (;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
    """))
    g1.get_root().set("CA", "latin-1")
    tc.assertEqual(g1.serialise(), dedent("""\
    (;FF[4]C[\xa3]CA[latin-1]GM[1]SZ[19])
    """))
    g1.get_root().set("CA", "unknown")
    tc.assertRaisesRegexp(ValueError, "unsupported charset: \['unknown']",
                          g1.serialise)

    # improperly-encoded from the start
    g2 = sgf.Sgf_game.from_string("""
    (;FF[4]C[£]CA[ascii]GM[1]SZ[19])
    """)
    tc.assertEqual(g2.serialise(), dedent("""\
    (;FF[4]C[£]CA[ascii]GM[1]SZ[19])
    """))
    g2.get_root().set("CA", "utf-8")
    tc.assertRaises(UnicodeDecodeError, g2.serialise)

    g3 = sgf.Sgf_game.from_string("""
    (;FF[4]C[Δ]CA[utf-8]GM[1]SZ[19])
    """)
    g3.get_root().unset("CA")
    tc.assertRaises(UnicodeEncodeError, g3.serialise)

def test_tree_mutation(tc):
    sgf_game = sgf.Sgf_game(9)
    root = sgf_game.get_root()
    n1 = root.new_child()
    n1.set("N", "n1")
    n2 = root.new_child()
    n2.set("N", "n2")
    n3 = n1.new_child()
    n3.set("N", "n3")
    n4 = root.new_child(1)
    n4.set("N", "n4")
    tc.assertEqual(
        sgf_game.serialise(),
        "(;FF[4]CA[UTF-8]GM[1]SZ[9](;N[n1];N[n3])(;N[n4])(;N[n2]))\n")
    tc.assertEqual(
        [node.get_raw_property_map() for node in sgf_game.main_sequence_iter()],
        [node.get_raw_property_map() for node in root, root[0], n3])
    tc.assertIs(sgf_game.get_last_node(), n3)

    n1.delete()
    tc.assertEqual(
        sgf_game.serialise(),
        "(;FF[4]CA[UTF-8]GM[1]SZ[9](;N[n4])(;N[n2]))\n")
    tc.assertRaises(ValueError, root.delete)

def test_tree_mutation_from_coarse_game(tc):
    sgf_game = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
    root = sgf_game.get_root()
    n4 = root.new_child()
    n4.set("N", "n4")
    n3 = root[0][0]
    tc.assertEqual(n3.get("N"), "n3")
    n5 = n3.new_child()
    n5.set("N", "n5")
    tc.assertEqual(sgf_game.serialise(),
                   "(;SZ[9](;N[n1];N[n3];N[n5])(;N[n2])(;N[n4]))\n")
    tc.assertEqual(
        [node.get_raw_property_map() for node in sgf_game.main_sequence_iter()],
        [node.get_raw_property_map() for node in root, root[0], n3, n5])
    tc.assertIs(sgf_game.get_last_node(), n5)
    n3.delete()
    tc.assertEqual(sgf_game.serialise(),
                   "(;SZ[9](;N[n1])(;N[n2])(;N[n4]))\n")
    tc.assertRaises(ValueError, root.delete)

def test_reparent(tc):
    g1 = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
    root = g1.get_root()
    # Test with unexpanded root
    tc.assertRaisesRegexp(ValueError, "would create a loop",
                          root.reparent, root)
    n1 = root[0]
    n2 = root[1]
    n3 = root[0][0]
    tc.assertEqual(n1.get("N"), "n1")
    tc.assertEqual(n2.get("N"), "n2")
    tc.assertEqual(n3.get("N"), "n3")
    n3.reparent(n2)
    tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n2];N[n3]))\n")
    n3.reparent(n2)
    tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n2];N[n3]))\n")
    tc.assertRaisesRegexp(ValueError, "would create a loop",
                          root.reparent, n3)
    tc.assertRaisesRegexp(ValueError, "would create a loop",
                          n3.reparent, n3)
    g2 = sgf.Sgf_game(9)
    tc.assertRaisesRegexp(
        ValueError, "new parent doesn't belong to the same game",
        n3.reparent, g2.get_root())

def test_reparent_index(tc):
    g1 = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
    root = g1.get_root()
    n1 = root[0]
    n2 = root[1]
    n3 = root[0][0]
    tc.assertEqual(n1.get("N"), "n1")
    tc.assertEqual(n2.get("N"), "n2")
    tc.assertEqual(n3.get("N"), "n3")
    n3.reparent(root, index=1)
    tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n3])(;N[n2]))\n")
    n3.reparent(root, index=1)
    tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n3])(;N[n2]))\n")
    n3.reparent(root, index=2)
    tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n2])(;N[n3]))\n")

def test_extend_main_sequence(tc):
    g1 = sgf.Sgf_game(9)
    for i in xrange(6):
        g1.extend_main_sequence().set("N", "e%d" % i)
    tc.assertEqual(
        g1.serialise(),
        "(;FF[4]CA[UTF-8]GM[1]SZ[9];N[e0];N[e1];N[e2];N[e3];N[e4];N[e5])\n")
    g2 = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
    for i in xrange(6):
        g2.extend_main_sequence().set("N", "e%d" % i)
    tc.assertEqual(
        g2.serialise(),
        "(;SZ[9](;N[n1];N[n3];N[e0];N[e1];N[e2];N[e3];N[e4];N[e5])(;N[n2]))\n")


def test_get_sequence_above(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    root = sgf_game.get_root()
    branchnode = root[0][0][0][0]
    leaf = branchnode[1][0][1]
    tc.assertEqual(sgf_game.get_sequence_above(root), [])

    tc.assertEqual(sgf_game.get_sequence_above(branchnode),
                   [root, root[0], root[0][0], root[0][0][0]])

    tc.assertEqual(sgf_game.get_sequence_above(leaf),
                   [root, root[0], root[0][0], root[0][0][0],
                    branchnode, branchnode[1], branchnode[1][0]])

    sgf_game2 = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    tc.assertRaisesRegexp(ValueError, "node doesn't belong to this game",
                          sgf_game2.get_sequence_above, leaf)

def test_get_main_sequence_below(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    root = sgf_game.get_root()
    branchnode = root[0][0][0][0]
    leaf = branchnode[1][0][1]
    tc.assertEqual(sgf_game.get_main_sequence_below(leaf), [])

    tc.assertEqual(sgf_game.get_main_sequence_below(branchnode),
                   [branchnode[0], branchnode[0][0], branchnode[0][0][0]])

    tc.assertEqual(sgf_game.get_main_sequence_below(root),
                   [root[0], root[0][0], root[0][0][0], branchnode,
                    branchnode[0], branchnode[0][0], branchnode[0][0][0]])

    sgf_game2 = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    tc.assertRaisesRegexp(ValueError, "node doesn't belong to this game",
                          sgf_game2.get_main_sequence_below, branchnode)

def test_main_sequence(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    root = sgf_game.get_root()

    nodes = list(sgf_game.main_sequence_iter())
    tc.assertEqual(len(nodes), 8)
    tc.assertIs(root.get_raw_property_map(),
                nodes[0].get_raw_property_map())
    # Check that main_sequence_iter() optimisation has been used.
    # (Have to call this before making the tree expand.)
    with tc.assertRaises(AttributeError):
        nodes[1].parent

    tree_nodes = sgf_game.get_main_sequence()
    tc.assertEqual(len(tree_nodes), 8)
    tc.assertIs(root.get_raw_property_map(),
                tree_nodes[0].get_raw_property_map())
    tc.assertIs(tree_nodes[0], root)
    tc.assertIs(tree_nodes[2].parent, tree_nodes[1])
    tc.assertIs(sgf_game.get_last_node(), tree_nodes[-1])

    tree_node = root
    for node in nodes:
        tc.assertIs(tree_node.get_raw_property_map(),
                    node.get_raw_property_map())
        if tree_node:
            tree_node = tree_node[0]

def test_find(tc):
    sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
    root = sgf_game.get_root()
    branchnode = root[0][0][0][0]
    leaf = branchnode[1][0][1]

    tc.assertEqual(root.get("VW"), set())
    tc.assertIs(root.find("VW"), root)
    tc.assertRaises(KeyError, root[0].get, "VW")
    tc.assertEqual(root[0].find_property("VW"), set())
    tc.assertIs(root[0].find("VW"), root)

    tc.assertEqual(branchnode.get("VW"),
                   set([(7, 0), (7, 1), (8, 0), (8, 1)]))
    tc.assertIs(branchnode.find("VW"), branchnode)
    tc.assertEqual(branchnode.find_property("VW"),
                   set([(7, 0), (7, 1), (8, 0), (8, 1)]))

    tc.assertRaises(KeyError, leaf.get, "VW")
    tc.assertIs(leaf.find("VW"), branchnode)
    tc.assertEqual(leaf.find_property("VW"),
                   set([(7, 0), (7, 1), (8, 0), (8, 1)]))

    tc.assertIs(leaf.find("XX"), None)
    tc.assertRaises(KeyError, leaf.find_property, "XX")

def test_node_set_raw(tc):
    sgf_game = sgf.Sgf_game.from_string(dedent(r"""
    (;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]
    PB[Black engine]PW[White engine]RE[W+R]SZ[9]
    AB[ai][bh][ee]AW[fd][gc]BM[2]VW[]
    PL[B]
    C[123abc]
    ;B[dg]C[first move])
    """))
    root = sgf_game.get_root()
    tc.assertEqual(root.get_raw('RE'), "W+R")
    root.set_raw('RE', "W+2.5")
    tc.assertEqual(root.get_raw('RE'), "W+2.5")
    tc.assertRaises(KeyError, root.get_raw, 'XX')
    root.set_raw('XX', "xyz")
    tc.assertEqual(root.get_raw('XX'), "xyz")

    root.set_raw_list('XX', ("abc", "def"))
    tc.assertEqual(root.get_raw('XX'), "abc")
    tc.assertEqual(root.get_raw_list('XX'), ["abc", "def"])

    tc.assertRaisesRegexp(ValueError, "empty property list",
                          root.set_raw_list, 'B', [])

    values = ["123", "456"]
    root.set_raw_list('YY', values)
    tc.assertEqual(root.get_raw_list('YY'), ["123", "456"])
    values.append("789")
    tc.assertEqual(root.get_raw_list('YY'), ["123", "456"])

    tc.assertRaisesRegexp(ValueError, "ill-formed property identifier",
                          root.set_raw, 'Black', "aa")
    tc.assertRaisesRegexp(ValueError, "ill-formed property identifier",
                          root.set_raw_list, 'Black', ["aa"])

    root.set_raw('C', "foo\\]bar")
    tc.assertEqual(root.get_raw('C'), "foo\\]bar")
    root.set_raw('C', "abc\\\\")
    tc.assertEqual(root.get_raw('C'), "abc\\\\")
    tc.assertRaisesRegexp(ValueError, "ill-formed raw property value",
                          root.set_raw, 'C', "foo]bar")
    tc.assertRaisesRegexp(ValueError, "ill-formed raw property value",
                          root.set_raw, 'C', "abc\\")
    tc.assertRaisesRegexp(ValueError, "ill-formed raw property value",
                          root.set_raw_list, 'C', ["abc", "de]f"])

    root.set_raw('C', "foo\\]bar\\\nbaz")
    tc.assertEqual(root.get('C'), "foo]barbaz")


def test_node_aliasing(tc):
    # Check that node objects retrieved by different means use the same
    # property map.

    sgf_game = sgf.Sgf_game.from_string(dedent(r"""
    (;C[root];C[node 1])
    """))
    root = sgf_game.get_root()
    plain_node = list(sgf_game.main_sequence_iter())[1]
    tree_node = root[0]
    # Check the main_sequence_iter() optimisation was used, otherwise this test
    # isn't checking what it's supposed to.
    tc.assertIsNot(tree_node, plain_node)
    tc.assertIs(tree_node.__class__, sgf.Tree_node)
    tc.assertIs(plain_node.__class__, sgf.Node)

    tc.assertEqual(tree_node.get_raw('C'), "node 1")
    tree_node.set_raw('C', r"test\value")
    tc.assertEqual(tree_node.get_raw('C'), r"test\value")
    tc.assertEqual(plain_node.get_raw('C'), r"test\value")

    plain_node.set_raw_list('XX', ["1", "2", "3"])
    tc.assertEqual(tree_node.get_raw_list('XX'), ["1", "2", "3"])

def test_node_set(tc):
    sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9])")
    root = sgf_game.get_root()
    root.set("KO", True)
    root.set("KM", 0.5)
    root.set('DD', [(3, 4), (5, 6)])
    root.set('AB', set([(0, 0), (1, 1), (4, 4)]))
    root.set('TW', set())
    root.set('XX', "nonsense [none]sense more n\\onsens\\e")

    tc.assertEqual(sgf_game.serialise(), dedent("""\
    (;FF[4]AB[ai][bh][ee]DD[ef][gd]GM[1]KM[0.5]KO[]SZ[9]TW[]
    XX[nonsense [none\\]sense more n\\\\onsens\\\\e])
    """))

def test_node_unset(tc):
    sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9]HA[3])")
    root = sgf_game.get_root()
    tc.assertEqual(root.get('HA'), 3)
    root.unset('HA')
    tc.assertRaises(KeyError, root.unset, 'PL')
    tc.assertEqual(sgf_game.serialise(),
                   "(;FF[4]GM[1]SZ[9])\n")

def test_set_and_unset_size(tc):
    g1 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9]HA[3])")
    root1 = g1.get_root()
    tc.assertRaisesRegexp(ValueError, "changing size is not permitted",
                          root1.set, "SZ", 19)
    root1.set("SZ", 9)
    tc.assertRaisesRegexp(ValueError, "changing size is not permitted",
                          root1.unset, "SZ")
    g2 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[19]HA[3])")
    root2 = g2.get_root()
    root2.unset("SZ")
    root2.set("SZ", 19)

def test_set_and_unset_charset(tc):
    g1 = sgf.Sgf_game.from_string("(;FF[4]CA[utf-8]GM[1]SZ[9]HA[3])")
    tc.assertEqual(g1.get_charset(), "UTF-8")
    root1 = g1.get_root()
    root1.unset("CA")
    tc.assertEqual(g1.get_charset(), "ISO-8859-1")
    root1.set("CA", "iso-8859-1")
    tc.assertEqual(g1.get_charset(), "ISO-8859-1")
    root1.set("CA", "ascii")
    tc.assertEqual(g1.get_charset(), "ASCII")
    root1.set("CA", "unknownencoding")
    tc.assertRaisesRegexp(ValueError,
                          "no codec available for CA unknownencoding",
                          g1.get_charset)

def test_node_set_move(tc):
    sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9];B[aa];B[bb])")
    root, n1, n2 = sgf_game.get_main_sequence()
    tc.assertEqual(root.get_move(), (None, None))
    root.set_move('b', (1, 1))
    n1.set_move('w', (1, 2))
    n2.set_move('b', None)
    tc.assertEqual(root.get('B'), (1, 1))
    tc.assertRaises(KeyError, root.get, 'W')
    tc.assertEqual(n1.get('W'), (1, 2))
    tc.assertRaises(KeyError, n1.get, 'B')
    tc.assertEqual(n2.get('B'), None)
    tc.assertRaises(KeyError, n2.get, 'W')

def test_node_setup_stones(tc):
    sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9]AW[aa:bb])")
    root = sgf_game.get_root()
    root.set_setup_stones(
        [(1, 2), (3, 4)],
        set(),
        [(1, 3), (4, 5)],
        )
    tc.assertEqual(root.get('AB'), set([(1, 2), (3, 4)]))
    tc.assertRaises(KeyError, root.get, 'AW')
    tc.assertEqual(root.get('AE'), set([(1, 3), (4, 5)]))

def test_add_comment_text(tc):
    sgf_game = sgf.Sgf_game(9)
    root = sgf_game.get_root()
    root.add_comment_text("hello\nworld")
    tc.assertEqual(root.get('C'), "hello\nworld")
    root.add_comment_text("hello\naga]in")
    tc.assertEqual(root.get('C'), "hello\nworld\n\nhello\naga]in")