import pathlib
import re
from collections import OrderedDict

import numpy as np

from ..data.points import Points
from ..data.nmr import FID, Spectrum
from ..utils.utils import staggered_range

NUMBERRE = re.compile(r'[0-9]\.*[0-9]*[Ee]*[+-]*[0-9]*')


class AsciiReader:
    delimiters = ['\t', ' ', ',']
    maxcount = 10

    def __init__(self, fname):
        self.fname = None
        self.header = []
        self.data = []
        self.delays = None
        self.shape = (0, 0)

        if fname:
            self.fname = pathlib.Path(fname)
            self.load_file()
            self.look_for_delay()

    def load_file(self, comments='#'):
        i = 0
        self.shape = (0, 0)
        with self.fname.open('r') as f:
            for line in f:
                line = line.rstrip('\n\t\r, ')
                if line.startswith(comments):
                    self.maxcount += 1
                    self.header.append(line.replace(comments, '').lstrip().rstrip())
                else:
                    if line == '':
                        continue
                    line = self.check_delimiters(line, AsciiReader.delimiters)
                    self.shape = (self.shape[0]+1, max(self.shape[1], len(line)))
                    self.data.append(line)
                i += 1
                if i == self.maxcount:
                    break

    def look_for_delay(self, fname=None):
        if fname is None:
            fname = self.fname.with_name('%s_delay.%s' % (self.fname.name, self.fname.suffix))

        try:
            self.delays = np.loadtxt(fname)
        except IOError:
            pass

    def calc_delays(self, start, end, nums, log=True, stagg=False, stag_size=1):
        if log:
            self.delays = np.geomspace(start, end, num=nums)
        else:
            self.delays = np.linspace(start, end, num=nums)

        if stagg:
            self.delays = staggered_range(self.delays, stepsize=stag_size)

    def export(self, xidx=None, yidx=None, yerridx=None, mode='Points') -> list:
        if xidx is None:
            xidx = [0]

        if yidx is None:
            yidx = list(range(0, self.shape[1]))
            yidx.remove(xidx[0])

        if yerridx is None:
            yerridx = []

        cols = xidx + yidx + yerridx
        raw_data = np.genfromtxt(self.fname, usecols=cols, missing_values='--')

        try:
            last_line = self.header[-1].split()
        except IndexError:
            last_line = []

        if self.delays is not None:
            if raw_data.shape[0] % len(self.delays) != 0:
                raise ValueError('number of delays {} does not fit into length of date {}'.format(
                    len(self.delays), raw_data.shape[1]))
            raw_data = raw_data.reshape((len(self.delays), -1, raw_data.shape[1]))
        else:
            raw_data = raw_data.reshape((1, *raw_data.shape))

        names = True if len(last_line) == raw_data.shape[1] else False
        only_f = self.fname.stem

        if self.delays:
            del_names = self.delays
        else:
            del_names = [only_f]

        ret_dic = OrderedDict()
        for j in range(raw_data.shape[0]):
            value = del_names[j]

            if mode == 'Points':
                try:
                    # TODO read errors correctly
                    for k in range(1, raw_data.shape[2]):
                        if raw_data.shape[2] < 3:
                            name = only_f
                        elif names:
                            name = last_line[k]
                        else:
                            name = only_f+'_'+str(k)
                        new_data = Points(raw_data[j, :, 0], raw_data[j, :, k], group=only_f,
                                          name=name, value=value, filename=self.fname)

                        self._update_dic(name, new_data, ret_dic)
                except IndexError:
                    new_data = Points(np.arange(0, raw_data.shape[1]), raw_data[0, :],
                                      name=only_f, group=str(only_f), filename=self.fname)

                    self._update_dic(only_f, new_data, ret_dic)
            else:
                cls = FID if mode == 'FID' else Spectrum

                try:
                    for k in range(1, raw_data.shape[2], 2):
                        if raw_data.shape[2] < 4:
                            name = only_f
                        elif names:
                            name = last_line[k]
                        else:
                            name = only_f + '_' + str(k)

                        new_data = cls(raw_data[j, :, 0], raw_data[j, :, k:k+2].T, value=value,
                                       group=name, name=str(value), filename=self.fname)
                        self._update_dic(name, new_data, ret_dic)

                except OverflowError:
                    new_data = cls(np.arange(0, raw_data.shape[1]), raw_data[0, :],
                                   group=only_f, name=str(only_f), filename=self.fname)
                    self._update_dic(only_f, new_data, ret_dic)

        return list(ret_dic.values())

    @staticmethod
    def _update_dic(key, value, old_dic):
        old_dic_keys = old_dic.keys()
        if key in old_dic_keys:
            counter = 0
            replace_key = key + str(counter)
            while replace_key in old_dic_keys:
                counter += 1
                replace_key = key + str(counter)
        else:
            replace_key = key
        old_dic[replace_key] = value

    @staticmethod
    def check_delimiters(line: str, delimiters: list):
        line = [line]

        for delim in delimiters:
            new_l = []
            for l in line:
                if delim in [' ', '\t']:
                    new_l.extend(l.split())
                else:
                    new_l.extend((l.split(delim)))
                line = new_l[:]

        ret_val = [l for l in line]

        return ret_val
