import numpy as np

from module.base.decorator import cached_property
from module.exception import ScriptError
from module.logger import logger
from module.map.map_grids import SelectedGrids
from module.os.globe_detection import GLOBE_MAP_SHAPE
from module.os.map_data import DIC_OS_MAP


class Zone:
    zone_id: int
    # Map shape, such as J10
    shape: str
    # Corrosion level, from 1 to 7
    hazard_level: int
    # Name in different servers
    cn: str
    en: str
    jp: str
    # Position where information bar is pinned on
    area_pos: tuple
    # area_pos + offset_pos is where mission pinned on
    offset_pos: tuple
    # 1 for upper-left, 2 for upper-right, 3 for bottom-left, 4 for bottom-right, 5 for center
    region: int

    is_port: int
    is_azur_port: int

    def __init__(self, zone_id, data):
        self.zone_id = zone_id
        self.__dict__.update(data)
        self.location = self.point_convert(self.area_pos)
        self.mission = self.point_convert(np.add(self.area_pos, self.offset_pos))
        self.is_port = self.zone_id in [0, 1, 2, 3, 4, 5, 6, 7]
        self.is_azur_port = self.zone_id in [0, 1, 2, 3]

    @staticmethod
    def point_convert(point):
        """
        Convert coordinates in world_chapter_colormask.lua to os_globe_map.png
        """
        point = np.multiply(point, 1.25)
        point = np.array((point[0], GLOBE_MAP_SHAPE[1] - point[1]))  # 1694 is the height of os_globe_map.png
        return point

    def __str__(self):
        """

        Returns:
            str: Such as `[3|圣彼得伯格|St. Petersburg|ペテルブルク]`
        """
        return f'[{self.zone_id}|{self.cn}|{self.en}|{self.jp}]'

    __repr__ = __str__

    def __eq__(self, other):
        return self.zone_id == other.zone_id


class ZoneManager:
    zone: Zone

    @cached_property
    def zones(self):
        """
        Returns:
            SelectedGrids:
        """
        return SelectedGrids([Zone(zone_id, info) for zone_id, info in DIC_OS_MAP.items()])

    def camera_to_zone(self, camera, region=None):
        """
        Args:
            camera (tuple): Point in os_globe_map.png
            region (int): Limit zone in specific region.

        Returns:
            Zone:
        """
        if region is None:
            zones = self.zones
        else:
            zones = self.zones.select(region=region)
        zones = zones.sort_by_camera_distance(camera=camera)
        return zones[0]

    def name_to_zone(self, name):
        """
        Args:
            name (str, int, Zone): Name in CN/EN/JP, zone id, or Zone instance.

        Returns:
            Zone:
        """
        if isinstance(name, Zone):
            return name
        elif isinstance(name, int):
            return self.zones.select(zone_id=name)[0]
        elif isinstance(name, str) and name.isdigit():
            return self.zones.select(zone_id=int(name))[0]
        else:
            def parse_name(n):
                n = str(n).replace(' ', '').lower()
                return n

            name = parse_name(name)
            for zone in self.zones:
                if name == parse_name(zone.cn) or name == parse_name(zone.en) or name == parse_name(zone.jp):
                    return zone
            logger.warning(f'Unable to find OS globe zone: {name}')
            raise ScriptError(f'Unable to find OS globe zone: {name}')

    def zone_nearest_azur_port(self, zone):
        """
        Args:
            zone (str, int, Zone): Name in CN/EN/JP, zone id, or Zone instance.

        Returns:
            Zone:
        """
        zone = self.name_to_zone(zone)
        ports = self.zones.select(is_azur_port=True).delete(SelectedGrids([self.zone]))
        # In same region
        for port in ports:
            if zone.region == port.region:
                return port
        # In different region
        ports = ports.sort_by_camera_distance(camera=tuple(zone.location))
        return ports[0]

    def zone_select(self, **kwargs):
        """
        Similar to `self.zone.select(**kwargs)`, but delete zones in region 5.

        Args:
            **kwargs: Properties of zone.

        Returns:
            SelectedGrids: SelectedGrids containing zone objects.
        """
        return self.zones.select(**kwargs).delete(self.zones.select(region=5))
