import unittest
import collections
from fontParts.base import FontPartsError


class TestPoint(unittest.TestCase):
    def getPoint_generic(self):
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((0, 0), "move")
        contour.appendPoint((101, 202), "line")
        point = contour.points[1]
        point.smooth = True
        return point

    # ----
    # repr
    # ----

    def test_reprContents(self):
        point = self.getPoint_generic()
        value = point._reprContents()
        self.assertIsInstance(value, list)
        for i in value:
            self.assertIsInstance(i, str)

    def test_reprContents_withName(self):
        point = self.getPoint_withName()
        value = point._reprContents()
        self.assertIsInstance(value, list)
        for i in value:
            self.assertIsInstance(i, str)

    def test_reprContents_isSmooth(self):
        point = self.getPoint_generic()
        point.smooth = True
        value = point._reprContents()
        self.assertIsInstance(value, list)
        for i in value:
            self.assertIsInstance(i, str)

    def test_reprContents_noContour(self):
        point, _ = self.objectGenerator("point")
        value = point._reprContents()
        self.assertIsInstance(value, list)
        for i in value:
            self.assertIsInstance(i, str)

    # -------
    # Parents
    # -------

    def test_get_parent_font(self):
        font, _ = self.objectGenerator("font")
        layer = font.newLayer("L")
        glyph = layer.newGlyph("X")
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        glyph.appendContour(contour)
        contour = glyph.contours[0]
        point = contour.points[0]
        self.assertIsNotNone(point.font)
        self.assertEqual(point.font, font)

    def test_get_parent_noFont(self):
        layer, _ = self.objectGenerator("layer")
        glyph = layer.newGlyph("X")
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        glyph.appendContour(contour)
        contour = glyph.contours[0]
        point = contour.points[0]
        self.assertIsNone(point.font)

    def test_get_parent_layer(self):
        layer, _ = self.objectGenerator("layer")
        glyph = layer.newGlyph("X")
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        glyph.appendContour(contour)
        contour = glyph.contours[0]
        point = contour.points[0]
        self.assertIsNotNone(point.layer)
        self.assertEqual(point.layer, layer)

    def test_get_parent_noLayer(self):
        glyph, _ = self.objectGenerator("glyph")
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        glyph.appendContour(contour)
        contour = glyph.contours[0]
        point = contour.points[0]
        self.assertIsNone(point.font)
        self.assertIsNone(point.layer)

    def test_get_parent_glyph(self):
        glyph, _ = self.objectGenerator("glyph")
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        glyph.appendContour(contour)
        contour = glyph.contours[0]
        point = contour.points[0]
        self.assertIsNotNone(point.glyph)
        self.assertEqual(point.glyph, glyph)

    def test_get_parent_noGlyph(self):
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        point = contour.points[0]
        self.assertIsNone(point.glyph)

    def test_get_parent_contour(self):
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        point = contour.points[0]
        self.assertIsNotNone(point.contour)
        self.assertEqual(point.contour, contour)

    def test_get_parent_noContour(self):
        point, _ = self.objectGenerator("point")
        self.assertIsNone(point.contour)

    def test_get_parent_segment(self):
        point, _ = self.objectGenerator("point")
        with self.assertRaises(AttributeError):
            point.segment

    def test_set_parent_contour(self):
        contour, _ = self.objectGenerator("contour")
        point, _ = self.objectGenerator("point")
        point.contour = contour
        self.assertIsNotNone(point.contour)
        self.assertEqual(point.contour, contour)

    def test_set_already_set_parent_contour(self):
        contour, _ = self.objectGenerator("contour")
        contour.appendPoint((10, 20))
        point = contour.points[0]
        contourOther, _ = self.objectGenerator("contour")
        with self.assertRaises(AssertionError):
            point.contour = contourOther

    def test_set_parent_contour_none(self):
        point, _ = self.objectGenerator("point")
        point.contour = None
        self.assertIsNone(point.contour)

    def test_get_parent_glyph_noContour(self):
        point, _ = self.objectGenerator("point")
        self.assertIsNone(point.glyph)

    def test_get_parent_layer_noContour(self):
        point, _ = self.objectGenerator("point")
        self.assertIsNone(point.layer)

    def test_get_parent_font_noContour(self):
        point, _ = self.objectGenerator("point")
        self.assertIsNone(point.font)

    # ----------
    # Attributes
    # ----------

    # type

    def test_get_type(self):
        point = self.getPoint_generic()
        self.assertEqual(point.type, "line")

    def test_set_move(self):
        point = self.getPoint_generic()
        point.type = "move"
        self.assertEqual(point.type, "move")

    def test_set_curve(self):
        point = self.getPoint_generic()
        point.type = "curve"
        self.assertEqual(point.type, "curve")

    def test_set_wcurve(self):
        point = self.getPoint_generic()
        point.type = "qcurve"
        self.assertEqual(point.type, "qcurve")

    def test_set_offcurve(self):
        point = self.getPoint_generic()
        point.type = "offcurve"
        self.assertEqual(point.type, "offcurve")

    def test_set_invalid_point_type_string(self):
        point = self.getPoint_generic()
        with self.assertRaises(ValueError):
            point.type = "xxx"

    def test_set_invalid_point_type_int(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.type = 123

    # smooth

    def test_get_smooth(self):
        point = self.getPoint_generic()
        self.assertEqual(point.smooth, True)

    def test_set_smooth_valid(self):
        point = self.getPoint_generic()
        point.smooth = True
        self.assertEqual(point.smooth, True)

    def test_set_smooth_invalid(self):
        point = self.getPoint_generic()
        with self.assertRaises(ValueError):
            point.smooth = "smooth"

    # x

    def test_get_x(self):
        point = self.getPoint_generic()
        self.assertEqual(point.x, 101)

    def test_set_x_valid_int(self):
        point = self.getPoint_generic()
        point.x = 100
        self.assertEqual(point.x, 100)

    def test_set_x_valid_float(self):
        point = self.getPoint_generic()
        point.x = 100.5
        self.assertEqual(point.x, 100.5)

    def test_set_x_invalidType(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.x = "100"

    # y

    def test_get_y(self):
        point = self.getPoint_generic()
        self.assertEqual(point.y, 202)

    def test_set_y_valid_int(self):
        point = self.getPoint_generic()
        point.y = 200
        self.assertEqual(point.y, 200)

    def test_set_y_valid_float(self):
        point = self.getPoint_generic()
        point.y = 200.5
        self.assertEqual(point.y, 200.5)

    def test_set_y_invalidType(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.y = "200"

    # --------------
    # Identification
    # --------------

    # index

    def getPoint_noParentContour(self):
        point, _ = self.objectGenerator("point")
        point.x = 101
        point.y = 202
        return point

    def test_get_index(self):
        point = self.getPoint_generic()
        self.assertEqual(point.index, 1)

    def test_get_index_noParentContour(self):
        point = self.getPoint_noParentContour()
        self.assertEqual(point.index, None)

    def test_set_index(self):
        point = self.getPoint_generic()
        with self.assertRaises(FontPartsError):
            point.index = 0

    # name

    def getPoint_withName(self):
        point = self.getPoint_generic()
        point.name = "P"
        return point

    def test_get_name_noName(self):
        point = self.getPoint_generic()
        self.assertEqual(point.name, None)

    def test_get_name_hasName(self):
        point = self.getPoint_withName()
        self.assertEqual(point.name, "P")

    def test_set_name_valid_str(self):
        point = self.getPoint_generic()
        point.name = "P"
        self.assertEqual(point.name, "P")

    def test_set_name_valid_none(self):
        point = self.getPoint_generic()
        point.name = None
        self.assertEqual(point.name, None)

    def test_set_name_invalidType(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.name = 1

    # identifier

    def test_identifier_get_none(self):
        point = self.getPoint_generic()
        self.assertIsNone(point.identifier)

    def test_identifier_generated_type(self):
        point = self.getPoint_generic()
        point.getIdentifier()
        self.assertIsInstance(point.identifier, str)

    def test_identifier_consistency(self):
        point = self.getPoint_generic()
        point.getIdentifier()
        # get: twice to test consistency
        self.assertEqual(point.identifier, point.identifier)

    def test_identifier_cannot_set(self):
        # identifier is a read-only property
        point = self.getPoint_generic()
        with self.assertRaises(FontPartsError):
            point.identifier = "ABC"

    def test_getIdentifer_no_contour(self):
        point, _ = self.objectGenerator("point")
        with self.assertRaises(FontPartsError):
            point.getIdentifier()

    def test_getIdentifer_consistency(self):
        point = self.getPoint_generic()
        point.getIdentifier()
        self.assertEqual(point.identifier, point.getIdentifier())

    # ----
    # Hash
    # ----

    def test_hash_object_self(self):
        point_one = self.getPoint_generic()
        self.assertEqual(hash(point_one), hash(point_one))

    def test_hash_object_other(self):
        point_one = self.getPoint_generic()
        point_two = self.getPoint_generic()
        self.assertNotEqual(hash(point_one), hash(point_two))

    def test_hash_object_self_variable_assignment(self):
        point_one = self.getPoint_generic()
        a = point_one
        self.assertEqual(hash(point_one), hash(a))

    def test_hash_object_other_variable_assignment(self):
        point_one = self.getPoint_generic()
        point_two = self.getPoint_generic()
        a = point_one
        self.assertNotEqual(hash(point_two), hash(a))

    def test_is_hashable(self):
        point_one = self.getPoint_generic()
        self.assertTrue(isinstance(point_one, collections.abc.Hashable))

    # --------
    # Equality
    # --------

    def test_object_equal_self(self):
        point_one = self.getPoint_generic()
        self.assertEqual(point_one, point_one)

    def test_object_not_equal_other(self):
        point_one = self.getPoint_generic()
        point_two = self.getPoint_generic()
        self.assertNotEqual(point_one, point_two)

    def test_object_equal_self_variable_assignment(self):
        point_one = self.getPoint_generic()
        a = point_one
        self.assertEqual(point_one, a)

    def test_object_not_equal_other_variable_assignment(self):
        point_one = self.getPoint_generic()
        point_two = self.getPoint_generic()
        a = point_one
        self.assertNotEqual(point_two, a)

    # ---------
    # Selection
    # ---------

    def test_selected_true(self):
        point = self.getPoint_generic()
        try:
            point.selected = False
        except NotImplementedError:
            return
        point.selected = True
        self.assertEqual(point.selected, True)

    def test_selected_false(self):
        point = self.getPoint_generic()
        try:
            point.selected = False
        except NotImplementedError:
            return
        self.assertEqual(point.selected, False)

    # ----
    # Copy
    # ----

    def test_copy_seperate_objects(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertIsNot(point, copied)

    def test_copy_different_contour(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertIsNot(point.contour, copied.contour)

    def test_copy_none_contour(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertEqual(copied.contour, None)

    def test_copy_same_type(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertEqual(point.type, copied.type)

    def test_copy_same_smooth(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertEqual(point.smooth, copied.smooth)

    def test_copy_same_x(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertEqual(point.x, copied.x)

    def test_copy_same_y(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertEqual(point.y, copied.y)

    def test_copy_same_name(self):
        point = self.getPoint_generic()
        copied = point.copy()
        self.assertEqual(point.name, copied.name)

    def test_copy_same_identifier_None(self):
        point = self.getPoint_generic()
        point._setIdentifier(None)
        copied = point.copy()
        self.assertEqual(point.identifier, copied.identifier)

    def test_copy_different_identifier(self):
        point = self.getPoint_generic()
        point.getIdentifier()
        copied = point.copy()
        self.assertNotEqual(point.identifier, copied.identifier)

    def test_copy_generated_identifier_different(self):
        otherContour, _ = self.objectGenerator("contour")
        point = self.getPoint_generic()
        copied = point.copy()
        copied.contour = otherContour
        point.getIdentifier()
        copied.getIdentifier()
        self.assertNotEqual(point.identifier, copied.identifier)

    def test_copyData_type(self):
        point = self.getPoint_generic()
        pointOther, _ = self.objectGenerator("point")
        pointOther.copyData(point)
        self.assertEqual(point.type, pointOther.type)

    def test_copyData_smooth(self):
        point = self.getPoint_generic()
        pointOther, _ = self.objectGenerator("point")
        pointOther.copyData(point)
        self.assertEqual(point.smooth, pointOther.smooth)

    def test_copyData_x(self):
        point = self.getPoint_generic()
        pointOther, _ = self.objectGenerator("point")
        pointOther.copyData(point)
        self.assertEqual(point.x, pointOther.x)

    def test_copyData_y(self):
        point = self.getPoint_generic()
        pointOther, _ = self.objectGenerator("point")
        pointOther.copyData(point)
        self.assertEqual(point.y, pointOther.y)

    def test_copyData_name(self):
        point = self.getPoint_generic()
        point.name = "P"
        pointOther, _ = self.objectGenerator("point")
        pointOther.copyData(point)
        self.assertEqual(point.name, pointOther.name)

    def test_copyData_different_identifier(self):
        point = self.getPoint_generic()
        point.getIdentifier()
        pointOther, _ = self.objectGenerator("point")
        pointOther.copyData(point)
        self.assertNotEqual(point.identifier, pointOther.identifier)

    # --------------
    # Transformation
    # --------------

    def test_transformBy_valid_no_origin(self):
        point = self.getPoint_generic()
        point.transformBy((2, 0, 0, 3, -3, 2))
        self.assertEqual((point.x, point.y), (199.0, 608.0))

    def test_transformBy_valid_origin(self):
        point = self.getPoint_generic()
        point.transformBy((2, 0, 0, 2, 0, 0), origin=(1, 2))
        self.assertEqual((point.x, point.y), (201.0, 402.0))

    def test_transformBy_invalid_one_string_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.transformBy((1, 0, 0, 1, 0, "0"))

    def test_transformBy_invalid_all_string_values(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.transformBy("1, 0, 0, 1, 0, 0")

    def test_transformBy_invalid_int_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.transformBy(123)

    # moveBy

    def test_moveBy_valid(self):
        point = self.getPoint_generic()
        point.moveBy((-1, 2))
        self.assertEqual((point.x, point.y), (100.0, 204.0))

    def test_moveBy_invalid_one_string_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.moveBy((-1, "2"))

    def test_moveBy_invalid_all_strings_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.moveBy("-1, 2")

    def test_moveBy_invalid_int_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.moveBy(1)

    # scaleBy

    def test_scaleBy_valid_one_value_no_origin(self):
        point = self.getPoint_generic()
        point.scaleBy(-2)
        self.assertEqual((point.x, point.y), (-202.0, -404.0))

    def test_scaleBy_valid_two_values_no_origin(self):
        point = self.getPoint_generic()
        point.scaleBy((-2, 3))
        self.assertEqual((point.x, point.y), (-202.0, 606.0))

    def test_scaleBy_valid_two_values_origin(self):
        point = self.getPoint_generic()
        point.scaleBy((-2, 3), origin=(1, 2))
        self.assertEqual((point.x, point.y), (-199.0, 602.0))

    def test_scaleBy_invalid_one_string_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.scaleBy((-1, "2"))

    def test_scaleBy_invalid_two_string_values(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.scaleBy("-1, 2")

    def test_scaleBy_invalid_tuple_too_many_values(self):
        point = self.getPoint_generic()
        with self.assertRaises(ValueError):
            point.scaleBy((-1, 2, -3))

    # rotateBy

    def test_rotateBy_valid_no_origin(self):
        point = self.getPoint_generic()
        point.rotateBy(45)
        self.assertEqual([(round(point.x, 3)), (round(point.y, 3))], [-71.418, 214.253])

    def test_rotateBy_valid_origin(self):
        point = self.getPoint_generic()
        point.rotateBy(45, origin=(1, 2))
        self.assertEqual([(round(point.x, 3)), (round(point.y, 3))], [-69.711, 214.132])

    def test_rotateBy_invalid_string_value(self):
        point = self.getPoint_generic()
        with self.assertRaises(TypeError):
            point.rotateBy("45")

    def test_rotateBy_invalid_too_large_value_positive(self):
        point = self.getPoint_generic()
        with self.assertRaises(ValueError):
            point.rotateBy(361)

    def test_rotateBy_invalid_too_large_value_negative(self):
        point = self.getPoint_generic()
        with self.assertRaises(ValueError):
            point.rotateBy(-361)

    # skewBy

    def test_skewBy_valid_no_origin_one_value(self):
        point = self.getPoint_generic()
        point.skewBy(100)
        self.assertEqual([(round(point.x, 3)), (round(point.y, 3))], [-1044.599, 202.0])

    def test_skewBy_valid_no_origin_two_values(self):
        point = self.getPoint_generic()
        point.skewBy((100, 200))
        self.assertEqual(
            [(round(point.x, 3)), (round(point.y, 3))], [-1044.599, 238.761]
        )

    def test_skewBy_valid_origin_one_value(self):
        point = self.getPoint_generic()
        point.skewBy(100, origin=(1, 2))
        self.assertEqual([(round(point.x, 3)), (round(point.y, 3))], [-1033.256, 202.0])

    def test_skewBy_valid_origin_two_values(self):
        point = self.getPoint_generic()
        point.skewBy((100, 200), origin=(1, 2))
        self.assertEqual(
            [(round(point.x, 3)), (round(point.y, 3))], [-1033.256, 238.397]
        )

    # -------------
    # Normalization
    # -------------

    # round

    def getPoint_floatXY(self):
        point, _ = self.objectGenerator("point")
        point.x = 101.3
        point.y = 202.6
        return point

    def test_roundX(self):
        point = self.getPoint_floatXY()
        point.round()
        self.assertEqual(point.x, 101)

    def test_roundY(self):
        point = self.getPoint_floatXY()
        point.round()
        self.assertEqual(point.y, 203)
