import pathlib
import re
from itertools import islice

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', ' ', ',']

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

        if fname:
            self.fname = pathlib.Path(fname)
            self.read_header()
            self.make_preview(10)
            self.look_for_delay()

    def read_header(self, comments='#'):
        with self.fname.open('r') as f:
            for line in f.readlines():
                line = line.rstrip('\n\t\r, ')
                if line.startswith(comments):
                    self.header.append(line.replace(comments, '').strip())
                else:
                    break

    def make_preview(self, num_lines: int):
        if num_lines <= len(self.data):
            return self.data[:num_lines], max(self.width[:num_lines])

        num_lines += len(self.header)
        with self.fname.open('r') as f:
            for i, line in enumerate(islice(f, len(self.header)+len(self.data), num_lines)):
                line = line.rstrip('\n\t\r, ')

                line = re.split('[\s,;]', line)
                try:
                    comment_start = line.index('#')
                    self.line_comment.append(' '.join(line[comment_start:]))
                    line = line[:comment_start]
                except ValueError:
                    self.line_comment.append('')

                self.width.append(len(line))
                self.data.append(line)

        return self.data, max(self.width)

    def look_for_delay(self, fname=None):
        if fname is None:
            fname = self.fname.with_name(f'{self.fname.name}_delay.{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, x: int = None, y: list = None, yerr: list = None,
               mode: str = 'Points', col_names=None) -> list:
        if yerr is None:
            yerr = []
        elif y is None:
            raise ValueError('y is None and yerr is not None')

        if (x is None) ^ (y is None):
            raise ValueError(f'x is {type(x)} and y is {type(y)}, should be both None or both defined')

        if x is None:
            x = [0]
        elif isinstance(x, int):
            x = [x]
        else:
            raise ValueError(f'x is {type(x)}  not int')

        if y is None:
            y = list(range(1, max(self.width)))

        cols = x + y + yerr
        raw_data = np.genfromtxt(self.fname, usecols=cols, missing_values='--')
        if raw_data.ndim == 1:
            # only one row or column
            if len(self.data) == 1:
                # one row
                raw_data = raw_data.reshape(1, -1)
            else:
                raw_data = raw_data.reshape(-1, 1)

        if self.delays is not None:
            if raw_data.shape[0] % len(self.delays) != 0:
                raise ValueError(f'number of delays {len(self.delays)} does not fit into length of date {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))

        filename = self.fname.stem

        if self.delays:
            delay_names = self.delays
        else:
            delay_names = [filename]

        imported_sets = []

        for i, value in enumerate(delay_names):
            kwargs = {'value': value, 'filename': self.fname, 'name': filename, 'group': filename, 'y_err': None}

            num_y = len(y)
            if mode == 'Points':
                single_len = 1
                cls = Points
                stepsize = 1
            elif mode == 'FID':
                cls = FID
                single_len = 2
                stepsize = 2
            elif mode == 'Spectrum':
                cls = Spectrum
                single_len = 2
                stepsize = 2
            else:
                raise ValueError(f'Unknown mode {mode}, mot ´Points´, ´FID´ or ´Spectrum´.')

            for j in range(1, num_y+1, stepsize):
                if col_names is not None:
                    # predefined name
                    kwargs['name'] = col_names[j-1]
                elif num_y > single_len:
                    # more than one axis, append column number
                    kwargs['name'] = filename + '_' + str(y[j-1])

                if j+num_y+1 < raw_data.shape[2]:
                    kwargs['y_err'] = raw_data[i, j+num_y+1]

                imported_sets.append(cls(x=raw_data[i, :, 0], y=raw_data[i, :, j:j+single_len].T, **kwargs))

        return imported_sets
