import unittest
import sys
from pathlib import Path

# Add `src` directory to the import paths
sys.path.insert(0, str(Path(__file__).parent.parent / 'src'))

from mapcss import parseDeclaration, MapCSS


class MapCSSTest(unittest.TestCase):
    def test_declarations(self):
        decl = parseDeclaration(""" linejoin: round; """)
        self.assertEqual(len(decl), 1)
        self.assertEqual(decl[0], {"linejoin": "round"})

        decl = parseDeclaration("""\tlinejoin :\nround ; """)
        self.assertEqual(len(decl), 1)
        self.assertEqual(decl[0], {"linejoin": "round"})

        decl = parseDeclaration(""" icon-image: parking_private-s.svg; text: "name"; """)
        self.assertEqual(len(decl), 1)
        self.assertEqual(decl[0], {
            "icon-image": "parking_private-s.svg",
            "text": "name"
        })

        decl = parseDeclaration("""
            pattern-offset: 90\t;
            pattern-image:\tarrow-m.svg   ;
            pattern-spacing: @trunk0 ;""")
        self.assertEqual(len(decl), 1)
        self.assertEqual(decl[0], {
            "pattern-offset": "90",
            "pattern-image": "arrow-m.svg",
            "pattern-spacing": "@trunk0",
        })

    def test_parse_variables(self):
        parser = MapCSS()
        parser.parse("""
@city_label: #999999;
@country_label: #444444;
@wave_length: 25;
""")
        self.assertEqual(parser.variables, {
            "city_label": "#999999",
            "country_label": "#444444",
            "wave_length": "25"
        })

    def test_parse_colors(self):
        parser = MapCSS()
        parser.parse("""
@city_label : #999999;
@country_label: #444444 ;
  @wave_length: 25;
""")
        self.assertEqual(parser.variables, {
            "city_label": "#999999",
            "country_label": "#444444",
            "wave_length": "25"
        })

    def test_parse_import(self):
        parser = MapCSS()
        mapcssFile = Path(__file__).parent / 'assets' / 'case-1-import' / 'main.mapcss'
        parser.parse(filename=str(mapcssFile))

        colors = parser.get_colors()
        self.assertEqual(colors, {
            "GuiText-color": (1.0, 1.0, 1.0),
            "GuiText-opacity": 0.7,
            "Route-color": (0.0, 0.0, 1.0),
            "Route-opacity": 0.5,
        })

    def test_parse_basic_chooser(self):
        parser = MapCSS()
        static_tags = {"tourism": True, "office": True,
                        "craft": True, "amenity": True}
        parser.parse("""
node|z17-[tourism],
area|z17-[tourism],
node|z18-[office],
area|z18-[office],
node|z18-[craft],
area|z18-[craft],
node|z19-[amenity],
area|z19-[amenity],
{text: name; text-color: #000030; text-offset: 1;}
""", static_tags=static_tags)

        self.assertEqual(len(parser.choosers), 1)
        self.assertEqual(len(parser.choosers[0].ruleChains), 8)

    def test_parse_basic_chooser_2(self):
        parser = MapCSS()
        static_tags = {"highway": True}
        parser.parse("""
@trunk0: #FF7326;

line|z6[highway=trunk],
line|z6[highway=motorway],
{color: @trunk0; opacity: 0.3;}
line|z7-9[highway=trunk],
line|z7-9[highway=motorway],
{color: @trunk0; opacity: 0.7;}
""", static_tags=static_tags)

        self.assertEqual(len(parser.choosers), 2)
        self.assertEqual(len(parser.choosers[0].ruleChains), 2)
        self.assertEqual(parser.choosers[0].ruleChains[0].subject, 'line')
        self.assertEqual(parser.choosers[0].selzooms, [6, 6])
        self.assertEqual(parser.choosers[1].selzooms, [7, 9])

        rule, object_id = parser.choosers[0].testChains({"highway": "trunk"})
        self.assertEqual(object_id, "::default")

    def test_parse_basic_chooser_3(self):
        parser = MapCSS()
        static_tags = {"addr:housenumber": True, "addr:street": False}
        parser.parse("""
/* Some Comment Here */

/*
   This sample is borrowed from Organic Maps Basemap_label.mapcss file
 */
node|z18-[addr:housenumber][addr:street]::int_name
{text: int_name; text-color: #65655E; text-position: center;}
""", static_tags=static_tags)

        building_tags = {"building": "yes", "addr:housenumber": "12", "addr:street": "Baker street"}

        # Check that mapcss parsed correctly
        self.assertEqual(len(parser.choosers), 1)
        styleChooser = parser.choosers[0]
        self.assertEqual(len(styleChooser.ruleChains), 1)
        self.assertEqual(styleChooser.selzooms, [18, 19])
        rule, object_id = styleChooser.testChains(building_tags)
        self.assertEqual(object_id, "::int_name")

        rule = styleChooser.ruleChains[0]
        self.assertEqual(rule.subject, 'node')
        self.assertEqual(rule.extract_tags(), {'addr:housenumber', 'addr:street'})

    def test_parse_basic_chooser_class(self):
        parser = MapCSS()
        parser.parse("""
way|z-13::*
{
  linejoin: round;
}
""")

        # Check that mapcss parsed correctly
        self.assertEqual(len(parser.choosers), 1)
        styleChooser = parser.choosers[0]
        self.assertEqual(len(styleChooser.ruleChains), 1)
        self.assertEqual(styleChooser.selzooms, [0, 13])
        rule, object_id = styleChooser.testChains({})
        self.assertEqual(object_id, "::*")

        rule = styleChooser.ruleChains[0]
        self.assertEqual(rule.subject, 'way')
        self.assertEqual(rule.extract_tags(), {'*'})

    def test_parse_basic_chooser_class_2(self):
        parser = MapCSS()
        parser.parse("""
way|z10-::*
{
  linejoin: round;
}
""")

        # Check that mapcss parsed correctly
        self.assertEqual(len(parser.choosers), 1)
        styleChooser = parser.choosers[0]
        self.assertEqual(len(styleChooser.ruleChains), 1)
        self.assertEqual(styleChooser.selzooms, [10, 19])
        rule, object_id = styleChooser.testChains({})
        self.assertEqual(object_id, "::*")

        rule = styleChooser.ruleChains[0]
        self.assertEqual(rule.subject, 'way')
        self.assertEqual(rule.extract_tags(), {'*'})

    def test_parse_basic_chooser_colors(self):
        parser = MapCSS()
        parser.parse("""
way|z-6::*
{
  linejoin: round;
}

colors {
  GuiText-color: #FFFFFF;
  GuiText-opacity: 0.7;
  MyPositionAccuracy-color: #FFFFFF;
  MyPositionAccuracy-opacity: 0.06;
  Selection-color: #FFFFFF;
  Selection-opacity: 0.64;
  Route-color: #0000FF;
  RouteOutline-color: #00FFFF;
}
""")

        # Check that colors from mapcss parsed correctly
        colors = parser.get_colors()
        self.assertEqual(colors, {
            "GuiText-color": (1.0, 1.0, 1.0),
            "GuiText-opacity": 0.7,
            "MyPositionAccuracy-color": (1.0, 1.0, 1.0),
            "MyPositionAccuracy-opacity": 0.06,
            "Selection-color": (1.0, 1.0, 1.0),
            "Selection-opacity": 0.64,
            "Route-color": (0.0, 0.0, 1.0),
            "RouteOutline-color": (0.0, 1.0, 1.0)
        })

    def test_parser_choosers_tree(self):
        parser = MapCSS()
        static_tags = {"tourism": True, "office": True,
                       "craft": True, "amenity": True}

        parser.parse("""
node|z17-[office=lawyer],
area|z17-[office=lawyer],
{text: name;text-color: #444444;text-offset: 1;font-size: 10;}

node|z17-[tourism],
area|z17-[tourism],
node|z18-[office],
area|z18-[office],
node|z18-[craft],
area|z18-[craft],
node|z19-[amenity],
area|z19-[amenity],
{text: name; text-color: #000030; text-offset: 1;}

node|z18-[office],
area|z18-[office],
node|z18-[craft],
area|z18-[craft],
{font-size: 11;}

node|z17-[office=lawyer],
area|z17-[office=lawyer]
{icon-image: lawyer-m.svg;}
""", static_tags=static_tags)

        for obj_type in ["line", "area", "node"]:
            parser.build_choosers_tree("tourism", obj_type, "tourism")
            parser.build_choosers_tree("office", obj_type, "office")
            parser.build_choosers_tree("craft", obj_type, "craft")
            parser.build_choosers_tree("amenity", obj_type, "amenity")

        parser.finalize_choosers_tree()

        # Pick style for zoom = 17
        styles18 = parser.get_style("office", "node", {"office": "lawyer"},
                                    zoom=18, xscale=1, zscale=1, filter_by_runtime_conditions=False)

        self.assertEqual(len(styles18), 1),
        self.assertEqual(styles18[0], {'object-id': '::default',
            'font-size': '11',
            'text': 'name',
            'text-color': (0, 0, 16*3/255),
            'text-offset': 1.0,
            'icon-image': 'lawyer-m.svg'})

        # Pick style for zoom = 17
        styles17 = parser.get_style("office", "node", {"office": "lawyer"},
                                    zoom=17, xscale=1, zscale=1, filter_by_runtime_conditions=False)

        self.assertEqual(len(styles17), 1),
        self.assertEqual(styles17[0], {'object-id': '::default',
            'font-size': '10',
            'text': 'name',
            'text-color': (68/255, 68/255, 68/255),
            'text-offset': 1.0,
            'icon-image': 'lawyer-m.svg'})

        # Pick style for zoom = 15
        styles15 = parser.get_style("office", "node", {"office": "lawyer"},
                                    zoom=15, xscale=1, zscale=1, filter_by_runtime_conditions=False)

        self.assertEqual(styles15, []),

    def test_parser_choosers_tree_with_classes(self):
        parser = MapCSS()
        static_tags = {"highway": True}

        parser.parse("""
line|z10-[highway=motorway]::shield,
line|z10-[highway=trunk]::shield,
line|z10-[highway=motorway_link]::shield,
line|z10-[highway=trunk_link]::shield,
line|z10-[highway=primary]::shield,
line|z11-[highway=primary_link]::shield,
line|z12-[highway=secondary]::shield,
line|z13-[highway=tertiary]::shield,
line|z15-[highway=residential]::shield,
{
  shield-font-size: 9;
  shield-text-color: #000000;
  shield-text-halo-radius: 0;
  shield-color: #FFFFFF;
  shield-outline-radius: 1;
}

line|z12-[highway=residential],
line|z12-[highway=tertiary],
line|z18-[highway=tertiary_link]
{
  text: name;
  text-color: #333333;
  text-halo-opacity: 0.8;
  text-halo-radius: 1;
}

line|z12-13[highway=residential],
line|z12-13[highway=tertiary]
{
    font-size: 12;
    text-color: #444444;
}
""", static_tags=static_tags)

        parser.build_choosers_tree("highway", "line", "highway")
        parser.finalize_choosers_tree()

        # Pick style for zoom = 10
        styles10 = parser.get_style("highway", "line", {"highway": "primary"},
                                    zoom=10, xscale=1, zscale=1, filter_by_runtime_conditions=False)

        self.assertEqual(len(styles10), 1),
        self.assertEqual(styles10[0], {'object-id': '::shield',
            'shield-font-size': '9',
            'shield-text-color': (0.0, 0.0, 0.0),
            'shield-text-halo-radius': 0.0,
            'shield-color': (1.0, 1.0, 1.0),
            'shield-outline-radius': 1.0})

        # Pick style for zoom = 15. Expecting two `object-id` values: '::shield' and '::default'
        styles15 = parser.get_style("highway", "line", {"highway": "tertiary"},
                                    zoom=15, xscale=1, zscale=1, filter_by_runtime_conditions=False)

        self.assertEqual(len(styles15), 2),
        self.assertEqual(styles15[0], {'object-id': '::shield',
            'shield-font-size': '9',
            'shield-text-color': (0.0, 0.0, 0.0),
            'shield-text-halo-radius': 0.0,
            'shield-color': (1.0, 1.0, 1.0),
            'shield-outline-radius': 1.0})
        self.assertEqual(styles15[1], {'object-id': '::default',
            'text': 'name',
            'text-color': (51/255, 51/255, 51/255),
            'text-halo-opacity': 0.8,
            'text-halo-radius': 1.0})

if __name__ == '__main__':
    unittest.main()