import shutil
import re
import os
from pathlib import Path, WindowsPath, PosixPath
from helpers.settings import MinorConvertException


def copy_file(parent_file, rel_path, output_parent, wildcards):
    """Copy file routine that will also expand glob patterns."""

    file_dest = None
    rel_path_ = globify(rel_path, wildcards)
    try:
        for file_src in parent_file.parent.glob(rel_path_):
            file_src = file_src.resolve()
            rp = os.path.relpath(file_src, parent_file.parent)
            file_dest = (output_parent.parent / rp).resolve()
            file_dest.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy(file_src, file_dest)
    except Exception as e:
        raise MinorConvertException(f'Error: {e}')

    return file_dest


def copy_file2(file_src, file_dest):
    """More basic copy file routine with a different signature. Does not expand glob."""

    try:
        file_dest.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy(file_src, file_dest)
    except Exception as e:
        raise MinorConvertException(f'Error: {e}')


def copy_rainfall_grid_csv(file_src, file_dest, settings):
    """
    Routine that will loop through a CSV file specified by READ GRID RF == <csv>
    and convert the grid names to have the correct extension.
    """

    from helpers.gis import get_database_name, gdal_format_2_ext

    try:
        regex = re.compile(r'\.(FLT|ASC|TIF|TIFF|GTIFF|TXT|DEM|GPKG).*', flags=re.IGNORECASE)
        out_ext = gdal_format_2_ext(settings.output_grid_format)
        file_dest.parent.mkdir(parents=True, exist_ok=True)
        with file_src.open() as f1:
            with file_dest.open('w') as f2:
                for i, line in enumerate(f1):
                    if i == 0:
                        f2.write(line)
                        continue

                    rf_timestep = line.split(',', 1)
                    if len(rf_timestep) < 2:
                        f2.write(line)
                        continue

                    time, source = rf_timestep

                    db, lyr = get_database_name(source)
                    if lyr is not None:
                        db = (TuflowPath(db).parent / lyr).with_suffix(TuflowPath(db).suffix)
                    new_source = regex.sub(out_ext, str(db))
                    new_line = f'{time},{new_source}'

                    f2.write(new_line)
    except Exception as e:
        settings.errors = True
        raise MinorConvertException(f'Error: {e}')


def globify(infile, wildcards):
    """Converts TUFLOW wildcards (variable names, scenario/event names) to '*' for glob pattern."""

    infile = str(infile)
    if wildcards is None:
        return infile

    for wc in wildcards:
        infile = re.sub(wc, '*', infile, flags=re.IGNORECASE)
    if re.findall(r'\*\*(?![\\/])', infile):
        infile = re.sub(re.escape(r'**'), '*', infile)

    return infile


def tuflowify_value(value, settings):
    """
    Converts READ GIS and READ GRID commands to all follow a
    database >> layername structure. Useful to keep everything consistent.

    Will correct GPKG specific commands that use '&&' and 'ALL' to database >> layername | database >> layername
    """

    from helpers.gis import get_all_layers_in_gpkg, get_database_name

    value = str(value)

    if value.count('>>') > value.count('<<'):
        if 'ALL' in value.upper():
            if '|' in value:
                lyrs = [x.strip() for x in value.split('|')]
            else:
                lyrs = [value]

            for i, lyr in enumerate(lyrs[:]):
                if lyr.count('>>') > lyr.count('<<'):
                    db, lyr_ = get_database_name(lyr)
                    if lyr_.upper() == 'ALL':
                        lyr_ = ' && '.join(get_all_layers_in_gpkg((settings.control_file.parent / db).resolve()))
                        lyrs[i] = f'{db} >> {lyr_}'

            value = ' | '.join(lyrs)

        if '&&' in value:
            if '|' in value:
                lyrs = [x.strip() for x in value.split('|')]
            else:
                lyrs = [value]
            lyrs_ = []
            for lyr in lyrs:
                if lyr.count('>>') > lyr.count('<<'):
                    db, lyr_ = get_database_name(lyr)
                    lyrs_.extend([f'{db} >> {x.strip()}' for x in lyr_.split('&&')])
                else:
                    lyrs_.append(lyr)
            return ' | '.join(lyrs_)
        elif '|' in value:
            return value
        else:
            db, lyr = get_database_name(value)
            db = (settings.control_file.parent / db).resolve()
    else:
        if '&&' in value:
            return value.replace('&&', '|')
        if '|' in value:
            return value

        db = TuflowPath(value)
        lyr = db.with_suffix('').name

        if db.suffix:
            db = (settings.control_file.parent / db).resolve()
        elif settings.read_spatial_database:
            db = settings.read_spatial_database
        else:
            db = db.with_suffix('.mif')
            db = (settings.control_file.parent / db).resolve()

    return f'{db} >> {lyr}'


def add_geom_suffix(name_, suffix):
    """Add geometry suffix to name if it doesn't already exist."""

    name_ = str(name_)

    if not suffix or re.findall(rf'{re.escape(suffix)}$', TuflowPath(name_).with_suffix("").name, flags=re.IGNORECASE):
        return name_

    return f'{TuflowPath(name_).with_suffix("")}{suffix}{TuflowPath(name_).suffix}'


def remove_geom_suffix(name_):
    """Removes geometry suffix from name if it exists."""

    new_name =  re.sub(r'_[PLR]$', '', str(TuflowPath(name_).with_suffix("")), flags=re.IGNORECASE)

    return TuflowPath(new_name).with_suffix(TuflowPath(name_).suffix)


def get_geom_suffix(name_):
    """Returns geometry suffix from the name if it exists."""

    if re.findall(r'_[PLR]$', str(TuflowPath(name_).with_suffix("")), flags=re.IGNORECASE):
        return re.findall(r'_[PLR]$', str(TuflowPath(name_).with_suffix("")), flags=re.IGNORECASE)[0]

    return ''


def change_gis_mi_folder(path, in_gis_format, out_gis_format):
    """Switches the mi and gis folder path depending on whether the output is MIF format or not."""

    from helpers.gis import GIS_MIF

    path = str(path)

    if in_gis_format == out_gis_format:
        return path

    if out_gis_format == GIS_MIF:
        return re.sub(r'(?:(?<=[\\/])|(?<=^))gis(?=[\\/])', 'mi', path, flags=re.IGNORECASE)
    else:
        return re.sub(r'(?:(?<=[\\/])|(?<=^))mi(?=[\\/])', 'gis', path, flags=re.IGNORECASE)


def replace_with_output_filepath(inpath, inparent, outparent, control_file):
    """Replace input path with output path."""

    if control_file is not None:
        return os.path.relpath(inpath, control_file.parent)
    else:
        relpath = TuflowPath(inpath).relative_to(TuflowPath(inparent))
        return (outparent / relpath).resolve()



class TuflowPath(Path):
    """
    Sub-class Path - 'Path' is nicer to use than 'os', but doesn't resolve TUFLOW wildcards/variables '<< >>' very well.
    So fix in this sub-class and we can still use Path and all the nice stuff it offers :)
    Currently only implemented for Windows.
    """

    def __new__(cls, *args, **kwargs):
        cls = TuflowWindowsPath if os.name == 'nt' else PosixPath
        self = cls._from_parts(args, init=False)
        self._init()
        return self

class TuflowWindowsPath(WindowsPath):

    def resolve(self, strict=...):
        """Override method so it will work with TUFLOW style wildcards/variables."""

        return TuflowPath(os.path.abspath(self))

    def glob(self, pattern, wildcards=()):
        """Override method so can do some stuff with TUFLOW style wildcards/variables and also GPKG databases."""

        from helpers.gis import get_database_name, GPKG

        if str(pattern).count('>>') > str(pattern).count('<<'):
            db, lyr = get_database_name(pattern)
        else:
            db, lyr = pattern, None

        for file in WindowsPath.glob(self, db):
            if file.suffix.upper() != '.GPKG':
                if lyr is not None:
                    yield TuflowWindowsPath(f'{file.resolve()} >> {file.with_suffix("").name}')
                else:
                    yield TuflowWindowsPath(file.resolve())
            else:
                lyr = globify(lyr, wildcards)
                for lyrname in GPKG(file).glob(lyr):
                    yield TuflowWindowsPath(f'{file.resolve()} >> {lyrname}')

    def re(self, pattern, flags=None):
        """Similar to glob, but use regular expression pattern."""

        for dir_, filenames in {dir_: [y for y in x] for dir_, _, x in os.walk(self)}.items():
            for filename in filenames:
                if re.findall(pattern, filename, flags=flags):
                    yield (TuflowWindowsPath(dir_) / filename).resolve()

    def find_parent(self, name_, index_=None):
        """
        Find parent with 'name_' that is directly up from the path. Index will limit how far up the path tree
        the search will go.

        e.g. index_=5 will only search 5 folders higher.
        """

        if index_ is None:
            for i, part in enumerate(self.parts[::-1]):
                if part.lower() == name_.lower():
                    return TuflowWindowsPath('.').joinpath(*self.parts[:-i])
        else:
            ind = -1 - index_
            for i, part in enumerate(self.parts[:ind:-1]):
                if part.lower() == name_.lower():
                    return TuflowWindowsPath('.').joinpath(*self.parts[:-i])

        return None

    def find_in_walk_dir(self, name_, index_=None):
        """
        Similar to find_parent but is not restrained by it having to be a direct parent, and will look into
        other folders.
         """

        if index_ is None:
            p = TuflowWindowsPath(self.parts[0])
        else:
            p = TuflowWindowsPath('.').joinpath(*self.parts[:-index_+1])

        folders = sum([[y.lower() for y in x] for _, x, _ in os.walk(p)], [])
        if name_.lower() in folders:
            return self._walk_dir(p, name_)

    def _walk_dir(self, dir_, name_):
        """Helper routine for 'find_in_walk_dir'."""

        for _, dirname, _ in os.walk(dir_):
            for dn in dirname:
                if dn.lower() == name_.lower():
                    return TuflowWindowsPath(os.path.join(dir_, dn))
                else:
                    dir_found = self._walk_dir(os.path.join(dir_, dn), name_)
                    if dir_found:
                        return dir_found
            break
