import io
import os.path
import typing
from collections import OrderedDict
from pathlib import Path

import numpy as np

from .converter import Converter
from .culvert_bend import CulvertBend
from .junction import Junction
from ..output import Output, OutputCollection
from ..helpers.tuflow_empty_files import tuflow_empty_field_map
from ..helpers.geometry import Point, Line, get_right_angle_line


if typing.TYPE_CHECKING:
    from ..parsers.units.river import River as RiverHandler


class River(Converter):

    def __init__(self, unit: 'RiverHandler' = None, *args, **kwargs) -> None:
        super().__init__(unit)
        if unit:
            self.ecf = Output('CONTROL', unit.uid)
            self.nwk = Output('GIS', unit.uid)
            self.tab = Output('GIS', unit.uid)
            self.xs = Output('FILE', unit.uid)

    @staticmethod
    def complete_unit_type_name() -> str:
        return 'RIVER_SECTION'

    def convert(self) -> OutputCollection:
        out_col = OutputCollection()
        if not self.needs_any_conversion():
            return out_col
        if not self.unit or not self.dat:
            return out_col
        if self.unit.dx > 0:
            out_col.append(self.get_nwk())
        out_col.append(self.get_xs())
        out_col.append(self.get_tab())
        out_col.append(self.get_ecf())
        return out_col

    def needs_any_conversion(self) -> bool:
        if self.unit.dx > 0:
            return True
        if self.unit.dns_units and self.unit.dns_units[0].type == 'JUNCTION':
            junc_unit = self.unit.dns_units[0]
            junc_ = Junction(junc_unit)
            dns_of_junc = junc_.dns_connections()
            ups_of_junc = junc_.ups_connections()
            if len(dns_of_junc) == 1 and dns_of_junc[0].type == 'RIVER' and len(ups_of_junc) == 1:
                return False
        return True

    def map_nwk_attributes(self, field_map: dict, unit: 'RiverHandler') -> OrderedDict:
        d = OrderedDict()
        for key, value in field_map.items():
            d[key] = None
        d['ID'] = unit.id
        d['Type'] = 'S'
        d['Len_or_ANA'] = unit.dx
        d['n_nf_Cd'] = 1.
        d['US_Invert'] = -99999.
        d['DS_Invert'] = -99999.
        bend_loss = CulvertBend.bend_loss(unit)
        if not np.isnan(bend_loss):
            d['Form_Loss'] = bend_loss
        d['Number_of'] = 1
        return d

    def map_tab_attributes(selfs, field_map: dict, unit: 'RiverHandler', tab_fpath: Path, xs_fpath: Path) -> OrderedDict:
        d = OrderedDict()
        for key, value in field_map.items():
            d[key] = None
        d['Source'] = Path(os.path.relpath(xs_fpath, tab_fpath.parent)).as_posix()
        d['Type'] = 'XZ'
        d['Flags'] = 'n'
        d['Column_1'] = 'x'
        d['Column_2'] = 'y'
        d['Column_3'] = 'n'
        return d

    def get_nwk(self) -> Output:
        self.nwk.fpath, self.nwk.lyrname = self.output_gis_file('1d_nwk', 'RIVER')
        self.nwk.field_map = tuflow_empty_field_map('1d_nwk')
        self.nwk.geom_type = 2  # ogr.wkbLineString
        self.nwk.content.geom = self.channel_geom(self.unit)
        self.nwk.content.attributes = self.map_nwk_attributes(self.nwk.field_map, self.unit)
        return self.nwk

    def get_xs(self) -> Output:
        self.xs.fpath = self.settings.output_dir / 'csv' / f'{self.unit.id}.csv'
        buf = io.StringIO()
        buf.write(f'! Generated by fm_to_estry. Source: {self.dat.name}/{self.unit.uid}\n')

        # deactivation zones
        idx_left = 0
        idx_right = self.unit.xs.shape[0]
        if 'deactivation_marker' in self.unit.xs.columns:
            idx = self.unit.xs.index[self.unit.xs.deactivation_marker.str.strip() == 'LEFT']
            if not idx.empty:
                idx_left = idx[0]
            idx = self.unit.xs.index[self.unit.xs.deactivation_marker.str.strip() == 'RIGHT']
            if not idx.empty:
                idx_right = min(idx[0] + 1, idx_right)
        xs = self.unit.xs[['x', 'y', 'n']].copy()
        if idx_left > 0:
            xs.iloc[:idx_left, 2] = xs.iloc[:idx_left, 2] * -1
        if idx_right < xs.shape[0]:
            xs.iloc[idx_right:, 2] = xs.iloc[idx_right:, 2] * -1

        xs.to_csv(buf, index=False, lineterminator='\n')
        self.xs.content = buf.getvalue()
        return self.xs

    def get_tab(self) -> Output:
        self.tab.fpath, self.tab.lyrname = self.output_gis_file('1d_xs', 'RIVER')
        self.tab.field_map = tuflow_empty_field_map('1d_tab')
        self.tab.geom_type = 2  # ogr.wkbLineString (gdal may not be installed)
        self.tab.content.geom = self.end_cross_section_geom(self.unit, avg_with_ups=True)
        self.tab.content.attributes = self.map_tab_attributes(self.tab.field_map, self.unit, self.tab.fpath, self.xs.fpath)
        return self.tab

    def get_ecf(self) -> Output:
        self.ecf.fpath = self.settings.output_dir / f'{self.settings.outname}.ecf'
        nwk_cmd = ''
        if self.nwk.fpath:
            nwk_cmd = 'Read GIS Network == {0}'.format(
                self.output_gis_ref(
                    Path(os.path.relpath(self.nwk.fpath, self.ecf.fpath.parent)).as_posix(), self.nwk.lyrname
                )
            )
        tab_cmd = 'Read GIS Table Links == {0}'.format(
            self.output_gis_ref(
                Path(os.path.relpath(self.tab.fpath, self.ecf.fpath.parent)).as_posix(), self.tab.lyrname
            )
        )
        if self.nwk.fpath:
            self.ecf.content = '\n'.join([nwk_cmd, tab_cmd])
        else:
            self.ecf.content = tab_cmd
        return self.ecf
