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

from fm_to_estry.helpers.geometry import generate_bridge_section, generate_bridge_losses
from fm_to_estry.helpers.tuflow_empty_files import tuflow_empty_field_map
from fm_to_estry.converters.converter import Converter
from fm_to_estry.output import Output, OutputCollection
from fm_to_estry.helpers.logging import get_fm2estry_logger


logger = get_fm2estry_logger()


if typing.TYPE_CHECKING:
    from fm_to_estry.parsers.units.bridge import Bridge as BridgeHandler


class PierLossBridge(Converter):

    def __init__(self, unit: 'BridgeHandler' = None) -> None:
        super().__init__(unit)
        if unit:
            self.ecf = Output('CONTROL', unit.uid)
            self.nwk = Output('GIS', unit.uid)
            self.xs = Output('FILE', unit.uid)
            self.bg = Output('FILE', unit.uid)
            self.tab = Output('GIS', unit.uid)
        self._bridge_section = None
        self._top_level = None

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

    def convert(self) -> OutputCollection:
        out_col = OutputCollection()
        out_col.append(self.get_nwk())
        out_col.append(self.get_xs())
        out_col.append(self.get_bg())
        out_col.extend(self.get_tab())
        out_col.append(self.get_ecf())
        return out_col

    def map_nwk_attributes(self, field_map: dict, unit: 'BridgeHandler') -> OrderedDict:
        d = OrderedDict()
        for key, value in field_map.items():
            d[key] = None
        d['ID'] = unit.uid
        d['Type'] = 'BB'
        d['US_Invert'] = -99999.
        d['DS_Invert'] = -99999.
        d['Number_of'] = 1
        return d

    def map_tab_attributes_xs(selfs, field_map: dict, unit: 'BridgeHandler', 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'] = ''
        d['Column_1'] = 'x'
        d['Column_2'] = 'y'
        return d

    def map_tab_attributes_bg(selfs, field_map: dict, unit: 'BridgeHandler', tab_fpath: Path, bg_fpath: Path) -> OrderedDict:
        d = OrderedDict()
        for key, value in field_map.items():
            d[key] = None
        d['Source'] = Path(os.path.relpath(bg_fpath, tab_fpath.parent)).as_posix()
        d['Type'] = 'BG'
        d['Flags'] = ''
        d['Column_1'] = 'x'
        d['Column_2'] = 'lc'
        return d

    def get_nwk(self) -> Output:
        self.nwk.fpath, self.nwk.lyrname = self.output_gis_file('1d_nwk', 'BRIDGE')
        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.uid}.csv'

        # use upstream xs if available, otherwise use downstream xs
        if not self.unit.xs_ups.empty:
            xs = self.unit.xs_ups.copy()
        elif not self.unit.xs_dns.empty:
            xs = self.unit.xs_dns.copy()
        else:
            logger.error(f'No XS data found for {self.unit.uid}')
            return self.xs

        # extract left/right bank markers and the corresponding top level elevations
        idx = xs.index[xs.chan_marker.str.strip() == 'L']
        if not idx.empty:
            i = idx[0]
            self._top_level = xs['top_level'].iloc[i]
        else:
            i = 0
        idx = xs.index[xs.chan_marker.str.strip() == 'R']
        if not idx.empty:
            j = idx[0]
            soffit = xs['top_level'].iloc[j]
            if self._top_level is not None:
                self._top_level = (self._top_level + soffit) / 2
        else:
            j = xs.shape[0] - 1
        left_idx, right_idx = i, j
        if self._top_level is None:
            self._top_level = xs.y.max()

        # bridge section with just columns x, y, obvert where obvert = average of top levels
        xs = xs[['x', 'y']]
        xs['top_level'] = self._top_level

        # generate bridge cross-section
        self._bridge_section = generate_bridge_section(xs, left_idx, right_idx, self.unit.piers, as_df=True)

        # write section to output object
        buf = io.StringIO()
        buf.write(f'! Generated by fm_to_estry. Source: {self.dat.name}/{self.unit.uid}\n')
        self._bridge_section.to_csv(buf, index=False, lineterminator='\n')
        self.xs.content = buf.getvalue()
        return self.xs

    def get_bg(self) -> Output:
        self.bg.fpath = self.settings.output_dir / 'csv' / f'{self.unit.uid}_bg.csv'
        bg = generate_bridge_losses(self._bridge_section, self._top_level, self.unit.piers,
                                    self.unit.k, 0., None, None, None,
                                    'ALIGNED', self.unit.npiers, as_df=True)
        bg[['lc']] = bg[['lc']] * self.unit.cali

        # write section to output object
        buf = io.StringIO()
        buf.write(f'! Generated by fm_to_estry. Source: {self.dat.name}/{self.unit.uid}\n')
        bg.to_csv(buf, index=False, lineterminator='\n')
        self.bg.content = buf.getvalue()
        return self.bg

    def get_tab(self) -> OutputCollection:
        out_col = OutputCollection()
        out_col.append(self.get_tab_xs())
        out_col.append(self.get_tab_bg())
        return out_col

    def get_tab_xs(self) -> Output:
        self.tab.fpath, self.tab.lyrname = self.output_gis_file('1d_bg', 'BRIDGE')
        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.mid_cross_section_geometry(self.unit, loc=0.5)
        self.tab.content.attributes = self.map_tab_attributes_xs(self.tab.field_map, self.unit, self.tab.fpath, self.xs.fpath)
        return self.tab

    def get_tab_bg(self) -> Output:
        tab = Output('GIS', self.unit.uid)
        tab.fpath, tab.lyrname = self.output_gis_file('1d_bg', 'BRIDGE')
        tab.field_map = tuflow_empty_field_map('1d_tab')
        tab.geom_type = 2  # ogr.wkbLineString (gdal may not be installed)
        tab.content.geom = self.mid_cross_section_geometry(self.unit, loc=0.6)
        tab.content.attributes = self.map_tab_attributes_bg(tab.field_map, self.unit, tab.fpath, self.bg.fpath)
        return tab

    def get_ecf(self) -> Output:
        self.ecf.fpath = self.settings.output_dir / f'{self.settings.outname}.ecf'
        self.ecf.content = '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
            )
        )
        self.ecf.content = '{0}\nRead GIS Table Links == {1}'.format(
            self.ecf.content,
            self.output_gis_ref(
                Path(os.path.relpath(self.tab.fpath, self.ecf.fpath.parent)).as_posix(), self.tab.lyrname
            )
        )
        return self.ecf
