import copy
import logging
import os
import subprocess

from maps_generator.generator.exceptions import OptionNotFound
from maps_generator.generator.exceptions import ValidationError
from maps_generator.generator.exceptions import wait_and_raise_if_fail

logger = logging.getLogger("maps_generator")


class GenTool:
    OPTIONS = {
        "dump_cities_boundaries": bool,
        "emit_coasts": bool,
        "fail_on_coasts": bool,
        "generate_cameras": bool,
        "generate_cities_boundaries": bool,
        "generate_cities_ids": bool,
        "generate_features": bool,
        "generate_geo_objects_features": bool,
        "generate_geo_objects_index": bool,
        "generate_geometry": bool,
        "generate_index": bool,
        "generate_isolines_info": bool,
        "generate_maxspeed": bool,
        "generate_packed_borders": bool,
        "generate_popular_places": bool,
        "generate_region_features": bool,
        "generate_regions": bool,
        "generate_regions_kv": bool,
        "generate_search_index": bool,
        "generate_traffic_keys": bool,
        "generate_world": bool,
        "have_borders_for_whole_world": bool,
        "make_city_roads": bool,
        "make_coasts": bool,
        "make_cross_mwm": bool,
        "make_routing_index": bool,
        "make_transit_cross_mwm": bool,
        "make_transit_cross_mwm_experimental": bool,
        "preprocess": bool,
        "split_by_polygons": bool,
        "stats_types": bool,
        "version": bool,
        "threads_count": int,
        "booking_data": str,
        "promo_catalog_cities": str,
        "brands_data": str,
        "brands_translations_data": str,
        "cache_path": str,
        "cities_boundaries_data": str,
        "data_path": str,
        "dump_wikipedia_urls": str,
        "geo_objects_features": str,
        "geo_objects_key_value": str,
        "ids_without_addresses": str,
        "idToWikidata": str,
        "intermediate_data_path": str,
        "isolines_path": str,
        "addresses_path": str,
        "nodes_list_path": str,
        "node_storage": str,
        "osm_file_name": str,
        "osm_file_type": str,
        "output": str,
        "planet_version": str,
        "popular_places_data": str,
        "regions_features": str,
        "regions_index": str,
        "regions_key_value": str,
        "srtm_path": str,
        "transit_path": str,
        "transit_path_experimental": str,
        "world_roads_path": str,
        "ugc_data": str,
        "uk_postcodes_dataset": str,
        "us_postcodes_dataset": str,
        "user_resource_path": str,
        "wikipedia_pages": str,
    }

    def __init__(
        self, name_executable, out=subprocess.DEVNULL, err=subprocess.DEVNULL, **options
    ):
        self.name_executable = name_executable
        self.subprocess = None
        self.output = out
        self.error = err
        self.options = {"threads_count": 1}
        self.logger = logger
        self.add_options(**options)

    @property
    def args(self):
        return self._collect_cmd()

    def add_options(self, **options):
        if "logger" in options:
            self.logger = options["logger"]

        for k, v in options.items():
            if k == "logger":
                continue

            if k not in GenTool.OPTIONS:
                raise OptionNotFound(f"{k} is unavailable option")

            if type(v) is not GenTool.OPTIONS[k]:
                raise ValidationError(
                    f"{k} required {str(GenTool.OPTIONS[k])},"
                    f" but not {str(type(v))}"
                )

            self.options[k] = str(v).lower() if type(v) is bool else v
        return self

    def run_async(self):
        assert self.subprocess is None, "You forgot to call wait()"
        cmd = self._collect_cmd()
        self.subprocess = subprocess.Popen(
            cmd, stdout=self.output, stderr=self.error, env=os.environ
        )

        self.logger.info(
            f"Run generator tool [{self.get_build_version()}]:" f" {' '.join(cmd)} "
        )
        return self

    def wait(self):
        code = self.subprocess.wait()
        self.subprocess = None
        return code

    def run(self):
        self.run_async()
        wait_and_raise_if_fail(self)

    def branch(self):
        c = GenTool(self.name_executable, out=self.output, err=self.error)
        c.options = copy.deepcopy(self.options)
        return c

    def get_build_version(self):
        p = subprocess.Popen(
            [self.name_executable, "--version"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            env=os.environ,
        )
        wait_and_raise_if_fail(p)
        out, err = p.communicate()
        return out.decode("utf-8").replace("\n", " ").strip()

    def _collect_cmd(self):
        options = ["".join(["--", k, "=", str(v)]) for k, v in self.options.items()]
        return [self.name_executable, *options]


def run_gen_tool(*args, **kwargs):
    GenTool(*args, **kwargs).run()
