import re
from pathlib import Path
from typing import IO, AnyStr, List, Optional, Union
import warnings

import pandas as pd

from pysprint.utils.exceptions import PySprintWarning

PathOrBuffer = Union[str, Path, IO[AnyStr]]


def _parse_single_line(line: str):
    possible_separators = (":", "_", "-", "\t", " ", ";", ",", ".")
    idx = 0
    parsed_line = (line,)
    while idx < len(possible_separators):
        res = line.split(possible_separators[idx])
        if len(res) == 2:
            parsed_line = res
            break
        idx += 1

    # if we fail with the possible separators, let's see if
    # we can split up on contiguous whitespace to two pieces.

    if len(parsed_line) == 1:
        ln = re.split(r'\s+', *parsed_line)
        if len(ln) == 2:
            parsed_line = ln
    rx = '[' + re.escape(''.join(possible_separators[2:-4])) + ']'

    # remove the obvious unwanted chars
    parsed_line = [element.strip("\n").strip("\x00") for element in parsed_line]

    # remove the separator-like chars
    parsed_line = [re.sub(rx, "", el) for el in parsed_line]

    # remove whitespace around elements
    parsed_line = [element.strip(" ") for element in parsed_line]

    return parsed_line


def _parse_metadata(filename, ref=None, sam=None, meta_len=1, encoding='utf-8'):
    _meta = {}
    with open(filename, encoding=encoding) as file:
        comm = next(file).strip("\n").split("-")[-1].lstrip(" ")
        additional = [
            _parse_single_line(next(file))
            for _ in range(1, meta_len + 1)
        ]
        if meta_len != 0:
            _meta = {"comment": comm}
        for el in additional:
            if len(el) == 2:
                _meta[el[0]] = el[1]
            else:
                # _meta.setdefault("unparsed", []).append(el)
                # should do the trick here
                if "unparsed" not in _meta:
                    _meta["unparsed"] = []
                _meta["unparsed"].append(el)
    if ref is not None:
        with open(ref, encoding=encoding) as file:
            comm = next(file).strip("\n").split("-")[-1].lstrip(" ")
            if meta_len != 0:
                _meta["reference_comment"] = comm
    if sam is not None:
        with open(sam, encoding=encoding) as file:
            comm = next(file).strip("\n").split("-")[-1].lstrip(" ")
            if meta_len != 0:
                _meta["sample_comment"] = comm
    return _meta


def _parse_raw(
        filename: PathOrBuffer,
        ref: Optional[PathOrBuffer] = None,
        sam: Optional[PathOrBuffer] = None,
        skiprows: int = 0,
        decimal: str = ".",
        sep: Optional[str] = None,
        delimiter: Optional[str] = None,
        comment: Optional[str] = None,
        usecols: Optional[List] = None,
        names=None,
        swapaxes=False,
        na_values=None,
        skip_blank_lines=True,
        keep_default_na=False,
        meta_len: int = 1
):
    if len([x for x in (ref, sam) if x is not None]) == 1:
        warnings.warn(
            "Reference and sample arm should be passed together or neither one.",
            PySprintWarning
        )
    if sep and delimiter:
        warnings.warn("Specified both `sep` and `delimiter`, using `delimiter` as default.")
        separator = delimiter
    else:
        separator = sep or delimiter or ","

    if comment is None:
        comment = "#"

    if usecols is None:
        usecols = [0, 1]

    if names is None:
        names = ["x", "y"]

    if skiprows < meta_len:
        warnings.warn(
            f"Skiprows is currently {skiprows}, but"
            f" meta information is set to {meta_len}"
            " lines. This implies that either one is probably wrong.",
            PySprintWarning,
        )

    _meta = _parse_metadata(filename, ref, sam, meta_len=meta_len)

    df = pd.read_csv(
        filename,
        skiprows=skiprows,
        sep=separator,
        decimal=decimal,
        usecols=usecols,
        names=names,
        comment=comment,
        na_values=na_values,
        skip_blank_lines=skip_blank_lines,
        keep_default_na=keep_default_na
    )
    if ref is not None and sam is not None:
        r = pd.read_csv(
            ref,
            skiprows=skiprows,
            sep=separator,
            decimal=decimal,
            usecols=usecols,
            names=names,
            comment=comment,
            na_values=na_values,
            skip_blank_lines=skip_blank_lines,
            keep_default_na=keep_default_na
        )
        s = pd.read_csv(
            sam,
            skiprows=skiprows,
            sep=separator,
            decimal=decimal,
            usecols=usecols,
            names=names,
            comment=comment,
            na_values=na_values,
            skip_blank_lines=skip_blank_lines,
            keep_default_na=keep_default_na
        )

        if swapaxes:
            return {
                "x": df["y"].values,
                "y": df["x"].values,
                "ref": r["x"].values,
                "sam": s["x"].values,
                "meta": _meta
            }
        return {
            "x": df["x"].values,
            "y": df["y"].values,
            "ref": r["y"].values,
            "sam": s["y"].values,
            "meta": _meta
        }
    if swapaxes:
        return {
            "x": df["y"].values,
            "y": df["x"].values,
            "meta": _meta
        }
    return {
        "x": df["x"].values,
        "y": df["y"].values,
        "meta": _meta
    }
