class Point(Vector2):
    def __init__(self, x: float, y: float, terrain: Terrain) -> None:
        super().__init__(x, y)
        self._terrain = terrain

    def latlng(self) -> LatLng:
        lat, lon = self._terrain._point_to_ll_transformer.transform(self.x, self.y)
        return LatLng(lat, lon)

    @staticmethod
    def from_latlng(latlng: LatLng, terrain: Terrain) -> Point:
        x, y = terrain._ll_to_point_transformer.transform(latlng.lat, latlng.lng)
        return Point(x, y, terrain)

    def new_in_same_map(self, x: float, y: float) -> Point:
        return Point(x, y, self._terrain)

    def point_from_heading(self, heading: float, distance: float) -> Point:
        x, y = point_from_heading(self.x, self.y, heading, distance)
        return Point(x, y, self._terrain)

    def midpoint(self, other: Vector2) -> Point:
        return (self + other) / 2

    def lerp(self, other: Vector2, t: float) -> Point:
        def _lerp(v0: float, v1: float, t: float) -> float:
            return (1 - t) * v0 + t * v1
        return Point(_lerp(self.x, other.x, t), _lerp(self.y, other.y, t), self._terrain)

    def heading_between_point(self, point: Vector2) -> float:
        return heading_between_points(self.x, self.y, point.x, point.y)

    def distance_to_point(self, point: Point) -> float:
        return _distance(self.x, self.y, point.x, point.y)

    def random_point_within(self, distance: float, min_distance: float = 0) -> Point:
        """Returns a random point within the given distance.

        This is a shortcut for Rectangle.from_point().random_point().

        Args:
            distance: max distance for the random point.
            min_distance: minimum distance the random point should have from origin

        Returns:
            Point: a new random point within the given distance from the point.
        """
        return self.point_from_heading(random.randrange(0, 360),
                                       random.random() * (distance - min_distance) + min_distance)

    def __add__(self, other: Vector2) -> Point:
        return Point(self.x + other.x, self.y + other.y, self._terrain)

    def __radd__(self, other: Union[float, Vector2]) -> Point:
        if isinstance(other, Vector2):
            return self + other
        return Point(self.x + other, self.y + other, self._terrain)

    def __sub__(self, other: Union[float, Vector2]) -> Point:
        if isinstance(other, Vector2):
            return Point(self.x - other.x, self.y - other.y, self._terrain)
        return Point(self.x - other, self.y - other, self._terrain)

    def __mul__(self, other: float) -> Point:
        return Point(self.x * other, self.y * other, self._terrain)

    def __rmul__(self, other: float) -> Point:
        return self * other

    def __truediv__(self, other: float) -> Point:
        return self * (1 / other)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __ne__(self, other: object) -> bool:
        return not self == other

    def __repr__(self) -> str:
        return f"Point({self.x!r}, {self.y!r}, {self._terrain!r})" class Vehicle(Unit):
    def __init__(self, terrain: Terrain, id: Optional[int] = None, name: Optional[str] = None, _type="Sandbox"):
        super().__init__(id, terrain, name, _type)
        self.player_can_drive = False
        self.transportable = {"randomTransportable": False}

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        super().load_from_dict(d)
        self.player_can_drive = d["playerCanDrive"]
        self.transportable = d["transportable"]

    def dict(self):
        d = super(Vehicle, self).dict()
        d["playerCanDrive"] = self.player_can_drive
        d["transportable"] = self.transportable
        return d class Static(Unit):
    def __init__(self, unit_id: int, name: Optional[str], _type: Union[str, Type[UnitType]], terrain: Terrain) -> None:
        from .planes import PlaneType
        from .helicopters import HelicopterType

        if isinstance(_type, str):
            _id = _type
        else:
            _id = _type.id

        super().__init__(unit_id, terrain, name, _id)
        self.skill = None
        self.shape_name: Optional[str] = None
        self.rate = None
        self.mass = None

        if not isinstance(_type, str):
            if issubclass(_type, StaticType):
                self.category = _type.category
                self.can_cargo = _type.can_cargo
                self.shape_name = _type.shape_name
                self.rate = _type.rate
                self.mass = 1000 if _type.can_cargo else None
            elif issubclass(_type, PlaneType):
                self.category = "Planes"
                self.can_cargo = False
            elif issubclass(_type, HelicopterType):
                self.category = "Helicopters"
                self.can_cargo = False
            elif issubclass(_type, ShipType):
                self.category = "Ships"
                self.can_cargo = False
            elif issubclass(_type, VehicleType):
                self.category = "Vehicles"
                self.can_cargo = False

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        super().load_from_dict(d)
        self.can_cargo = d.get("canCargo", False)
        self.category = d["category"]
        self.shape_name = d.get("shape_name", None)
        self.rate = d.get("rate")
        self.mass = d.get("mass")

    def dict(self):
        d = super(Static, self).dict()
        d["category"] = self.category
        d["canCargo"] = self.can_cargo
        if self.shape_name is not None:
            d["shape_name"] = self.shape_name
        if self.rate is not None:
            d["rate"] = self.rate
        if self.mass is not None:
            d["mass"] = self.mass
        return d class Ship(Unit):
    def __init__(self, terrain: Terrain, id=None, name: Optional[str] = None, _type=None):
        super().__init__(id, terrain, name, _type.id)
        self.transportable = {"randomTransportable": False}
        self.frequency = 127500000

    def set_frequency(self, frequency: int) -> None:
        """Sets the communications frequency for this unit.

        Args:
            frequency: The frequency of the communications channel in hertz.
        """
        self.frequency = frequency

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        super().load_from_dict(d)
        self.frequency = d.get("frequency", self.frequency)
        self.transportable = d["transportable"]

    def dict(self):
        d = super(Ship, self).dict()
        d["frequency"] = self.frequency
        d["transportable"] = self.transportable
        return d class FARP(BaseFARP):
    def __init__(
        self,
        terrain: Terrain,
        unit_id=None,
        name: Optional[str] = None,
        frequency=127.5,
        modulation=0,
        callsign_id=1
    ) -> None:
        super().__init__(unit_id, name, "FARP", "FARPS", frequency, modulation, callsign_id, terrain)

    def load_from_dict(self, d):
        super(FARP, self).load_from_dict(d)
        self.heliport_frequency = float(d.get("heliport_frequency", 127.5))
        self.heliport_modulation = d.get("heliport_modulation", 0)
        self.heliport_callsign_id = d.get("heliport_callsign_id", 0)

    def dict(self):
        d = super(FARP, self).dict()
        d["heliport_frequency"] = self.heliport_frequency
        d["heliport_modulation"] = self.heliport_modulation
        d["heliport_callsign_id"] = self.heliport_callsign_id

        return d class SingleHeliPad(BaseFARP):
    def __init__(
        self,
        terrain: Terrain,
        unit_id=None,
        name: Optional[str] = None,
        frequency=127.5,
        modulation=0,
        callsign_id=1
    ) -> None:
        super().__init__(unit_id, name, "SINGLE_HELIPAD", "FARP", frequency, modulation, callsign_id, terrain)

    def load_from_dict(self, d):
        super(SingleHeliPad, self).load_from_dict(d)
        self.heliport_frequency = float(d.get("heliport_frequency", 127.5))
        self.heliport_modulation = d.get("heliport_modulation", 0)
        self.heliport_callsign_id = d.get("heliport_callsign_id", 0)

    def dict(self):
        d = super(SingleHeliPad, self).dict()
        d["heliport_frequency"] = self.heliport_frequency
        d["heliport_modulation"] = self.heliport_modulation
        d["heliport_callsign_id"] = self.heliport_callsign_id

        return d class Plane(FlyingUnit):
    def __init__(self, terrain: Terrain, _id=None, name=None, _type: Type[PlaneType] = A_10C, _country=None):
        super().__init__(_id, name, _type, terrain, _country) class Helicopter(FlyingUnit):
    def __init__(self, terrain: Terrain, _id=None, name=None, _type: Type[HelicopterType] = Ka_50, _country=None):
        super().__init__(_id, name, _type, terrain, _country)
        self.rope_length = 15

    def load_from_dict(self, d):
        super(Helicopter, self).load_from_dict(d)
        if "ropeLength" in d:
            self.rope_length = d["ropeLength"]

    def dict(self):
        d = super(Helicopter, self).dict()
        if self.rope_length:
            d["ropeLength"] = self.rope_length
        return d class MovingPoint(StaticPoint):
    def __init__(self, position: mapping.Point) -> None:
        super().__init__(position)
        self.type = "Turning Point"
        self.action: PointAction = PointAction.TurningPoint
        self.alt_type = "BARO"
        self.ETA = 0
        self.ETA_locked = True
        self.speed_locked = True
        self.tasks: List[task.Task] = []
        self.properties: Optional[PointProperties] = None
        self.airdrome_id: Optional[int] = None
        self.helipad_id = None
        self.link_unit = None

    def load_from_dict(self, d, translation):
        super(MovingPoint, self).load_from_dict(d, translation)
        self.alt_type = d.get("alt_type", None)
        self.ETA_locked = d["ETA_locked"]
        self.ETA = d["ETA"]
        self.speed_locked = d["speed_locked"]
        if d.get("task") is not None:
            task_keys = sorted(d["task"]["params"]["tasks"].keys())
            for t in task_keys:
                self.tasks.append(task._create_from_dict(d["task"]["params"]["tasks"][t]))
        self.airdrome_id = d.get("airdromeId", None)
        self.helipad_id = d.get("helipadId", None)
        self.link_unit = d.get("linkUnit", None)
        if d.get("properties"):
            self.properties = PointProperties()
            self.properties.load_from_dict(d.get("properties"))
        else:
            self.properties = None

    def find_task(self, task_type):
        """Searches tasks in this point for the given task class

        :param task_type: task class to search, :py:mod:`dcs.task`
        :return: task instance if found, else None
        """
        for t in self.tasks:
            if isinstance(t, task_type):
                return t
        return None

    def add_task(self, task_: task.Task):
        self.tasks.append(task_)

    def dict(self):
        d = super(MovingPoint, self).dict()
        d["alt_type"] = self.alt_type
        d["ETA"] = self.ETA
        d["ETA_locked"] = self.ETA_locked
        d["speed_locked"] = self.speed_locked
        tasks = {}
        for i in range(0, len(self.tasks)):
            self.tasks[i].number = i + 1
            tasks[i + 1] = self.tasks[i].dict()
        d["task"] = {
            "id": "ComboTask",
            "params": {
                "tasks": {i + 1: self.tasks[i].dict() for i in range(0, len(self.tasks))}
            }
        }
        if self.airdrome_id is not None:
            d["airdromeId"] = self.airdrome_id
        if self.helipad_id is not None:
            d["helipadId"] = self.helipad_id
        if self.link_unit is not None:
            d["linkUnit"] = self.link_unit
        if self.properties is not None:
            d["properties"] = self.properties.dict()
        return d class StaticPoint:
    def __init__(self, position: mapping.Point) -> None:
        self.position = copy.copy(position)
        self.alt = 0
        self.type = ""
        self.name: str = ""
        self.speed = 0.0
        self.formation_template = ""
        self.action = PointAction.None_  # type: PointAction
        self.landing_refuel_rearm_time: Optional[int] = None

    def load_from_dict(self, d: Dict[str, Any], translation) -> None:
        self.alt = d["alt"]
        self.type = d["type"]
        self.position.x = d["x"]
        self.position.y = d["y"]
        self.action = PointAction(d["action"])
        self.formation_template = d["formation_template"]
        self.speed = d["speed"]
        if "name" in d:
            if d["name"].startswith("DictKey_Translation_"):
                self.name = str(translation.get_string(d["name"]))
            else:
                self.name = d["name"]
        try:
            self.landing_refuel_rearm_time = int(d["timeReFuAr"])
        except KeyError:
            self.landing_refuel_rearm_time = None

    def dict(self) -> Dict[str, Any]:
        if not isinstance(self.name, str):
            raise TypeError("Point name expected to be `str`")
        d = {
            "alt": self.alt,
            "type": self.type,
            "name": self.name,
            "x": self.position.x,
            "y": self.position.y,
            "speed": round(self.speed, 13),
            "formation_template": self.formation_template,
            "action": self.action.value
        }

        if self.landing_refuel_rearm_time is not None:
            d["timeReFuAr"] = self.landing_refuel_rearm_time

        return d class Country:
    callsign: Dict[str, List[str]] = {}
    planes: List[Type[PlaneType]] = []
    helicopters: List[Type[HelicopterType]] = []

    def __init__(self, _id, name):
        self.id = _id
        self.name = name
        self.vehicle_group = []  # type: List[VehicleGroup]
        self.ship_group = []  # type: List[ShipGroup]
        self.plane_group = []  # type: List[PlaneGroup]
        self.helicopter_group = []  # type: List[HelicopterGroup]
        self.static_group = []  # type: List[StaticGroup]
        self.current_callsign_id = 99
        self.current_callsign_category = {}  # type: Dict[str,int]
        self._tail_numbers: Set[str] = set()

    def add_vehicle_group(self, vgroup):
        self.vehicle_group.append(vgroup)

    def add_ship_group(self, sgroup):
        self.ship_group.append(sgroup)

    def add_plane_group(self, pgroup):
        self.plane_group.append(pgroup)

    def add_helicopter_group(self, hgroup):
        self.helicopter_group.append(hgroup)

    def add_aircraft_group(self, group: FlyingGroup) -> None:
        if group.units[0].unit_type.helicopter:
            assert isinstance(group, HelicopterGroup)
            self.helicopter_group.append(group)
        else:
            assert isinstance(group, PlaneGroup)
            self.plane_group.append(group)

    def add_static_group(self, sgroup):
        self.static_group.append(sgroup)

    def remove_static_group(self, sgroup):
        for i in range(0, len(self.static_group)):
            if sgroup.id == self.static_group[i].id:
                del self.static_group[i]
                return True

        return False

    def find_group(self, group_name, search="exact"):
        groups = [self.vehicle_group,
                  self.ship_group,
                  self.plane_group,
                  self.helicopter_group,
                  self.static_group]
        for search_group in groups:
            for group in search_group:
                if find_map[search](group.name, group_name):
                    return group
        return None

    def find_vehicle_group(self, name: str, search="exact"):
        for group in self.vehicle_group:
            if find_map[search](group.name, name):
                return group
        return None

    def find_ship_group(self, name: str, search="exact"):
        for group in self.ship_group:
            if find_map[search](group.name, name):
                return group
        return None

    def find_plane_group(self, name: str, search="exact"):
        for group in self.plane_group:
            if find_map[search](group.name, name):
                return group

    def find_helicopter_group(self, name: str, search="exact"):
        for group in self.helicopter_group:
            if find_map[search](group.name, name):
                return group

    def find_static_group(self, name: str, search="exact"):
        for group in self.static_group:
            if find_map[search](group.name, name):
                return group
        return None

    def vehicle_group_within(self, point, distance) -> List[Group]:
        """Return all vehicle groups within the radius of a given point.

        Args:
            point(mapping.Point): Center of circle
            distance: Distance to the point

        Returns:
            Sequence of vehicle groups within range.
        """
        return [x for x in self.vehicle_group if x.position.distance_to_point(point) < distance]

    def static_group_within(self, point, distance) -> List[Group]:
        """Return all static groups within the radius of a given point.

        Args:
            point(mapping.Point): Center of circle
            distance: Distance to the point

        Returns:
            Sequence of static groups within range.
        """
        return [x for x in self.static_group if x.position.distance_to_point(point) < distance]

    def next_callsign_id(self):
        self.current_callsign_id += 1
        return self.current_callsign_id

    def next_callsign_category(self, category):
        if category not in self.current_callsign_category:
            self.current_callsign_category[category] = 0
            return self.callsign.get(category)[0]

        self.current_callsign_category[category] += 1
        if self.current_callsign_category[category] >= len(self.callsign[category]):
            self.current_callsign_category[category] = 0
        return self.callsign.get(category)[self.current_callsign_category[category]]

    @property
    def unused_onboard_numbers(self) -> Set[str]:
        return self._tail_numbers

    def reset_onboard_numbers(self):
        """
        Resets/clears reserved onboard numbers for this country.
        :return:
        """
        self._tail_numbers = set()

    def reserve_onboard_num(self, number: str) -> bool:
        """
        Reserve the give onboard_num (tail number), if already used return True.
        :param int number: onboard num
        :return: True if number is already in use, else False
        """
        is_in = number in self._tail_numbers
        self._tail_numbers.add(number)
        return is_in

    def next_onboard_num(self) -> str:
        free_set = {"{:03}".format(x) for x in range(10, 999)} - self._tail_numbers
        tailnum = free_set.pop()
        self.reserve_onboard_num(tailnum)
        return tailnum

    def dict(self):
        d = {
            "name": self.name,
            "id": self.id
        }

        if self.vehicle_group:
            d["vehicle"] = {"group": {}}
            i = 1
            for vgroup in self.vehicle_group:
                d["vehicle"]["group"][i] = vgroup.dict()
                i += 1

        if self.ship_group:
            d["ship"] = {"group": {}}
            i = 1
            for group in self.ship_group:
                d["ship"]["group"][i] = group.dict()
                i += 1

        if self.plane_group:
            d["plane"] = {"group": {}}
            i = 1
            for plane_group in self.plane_group:
                d["plane"]["group"][i] = plane_group.dict()
                i += 1

        if self.helicopter_group:
            d["helicopter"] = {"group": {}}
            i = 1
            for group in self.helicopter_group:
                d["helicopter"]["group"][i] = group.dict()
                i += 1

        if self.static_group:
            d["static"] = {"group": {}}
            i = 1
            for static_group in self.static_group:
                d["static"]["group"][i] = static_group.dict()
                i += 1
        return d

    def __str__(self):
        return str(self.id) + "," + self.name + "," + str(self.vehicle_group) class StatusMessage:
    def __init__(self, message: str, type_: MessageType, severity: MessageSeverity = MessageSeverity.ERROR):
        self._message = message
        self._type = type_
        self._severity = severity

    @property
    def type(self) -> MessageType:
        return self._type

    @property
    def severity(self) -> MessageSeverity:
        return self._severity

    @property
    def message(self) -> str:
        return self._message class MessageType(IntEnum):
    ONBOARD_NUM_DUPLICATE = 0
    PARKING_SLOT_NOT_VALID = 1
    PARKING_SLOTS_FULL = 3
    MISSION_FORMAT_OLD = 4 class MessageSeverity(IntEnum):
    INFO = 0
    WARN = 1
    ERROR = 2
import sys
import dcs.countries as countries
import dcs.unitgroup as unitgroup
import dcs.planes as planes
import dcs.helicopters as helicopters
import dcs.ships as ships
from typing import Dict, Union, List, TYPE_CHECKING
from dcs.mapping import Point
from dcs.unit import Vehicle, Static, Ship, FARP, SingleHeliPad
from dcs.flyingunit import Plane, Helicopter
from dcs.point import MovingPoint, StaticPoint
from dcs.country import Country
from dcs.status_message import StatusMessage, MessageType, MessageSeverity
    from . import Mission

if TYPE_CHECKING:


class Coalition:
    def __init__(self, name, bullseye=None):
        self.name = name
        self.countries = {}  # type: Dict[str, Country]
        self.bullseye = bullseye
        self.nav_points = []  # TODO

    @staticmethod
    def _sort_keys(points):
        keys = []
        for imp_point_idx in points:
            keys.append(int(imp_point_idx))

        keys.sort()
        return keys

    @staticmethod
    def _import_moving_point(mission, group: unitgroup.Group, imp_group) -> unitgroup.Group:
        keys = Coalition._sort_keys(imp_group["route"]["points"])

        for imp_point_idx in keys:
            imp_point = imp_group["route"]["points"][imp_point_idx]

            point = MovingPoint(Point(0, 0, mission.terrain))