# -*- coding: utf-8 -*-
# Copyright 2007-2020 The HyperSpy developers
#
# This file is part of HyperSpy.
#
# HyperSpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# HyperSpy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with HyperSpy.  If not, see <http://www.gnu.org/licenses/>.


import numpy as np
import os
from datetime import datetime as dt
import warnings
import logging
from distutils.version import LooseVersion


# Plugin characteristics
# ----------------------
format_name = 'Protochips'
description = 'Reads Protochips log files (heating/baising and gas cell)'
full_support = False
# Recognised file extension
file_extensions = ['csv', 'CSV']
default_extension = 0
# Reading capabilities
reads_images = False
reads_spectrum = False
reads_spectrum_image = False
# Writing capabilities
writes = False

_logger = logging.getLogger(__name__)


# At some point, if there is another readerw, whith also use csv file, it will
# be necessary to mention the other reader in this message (and to add an
# argument in the load function to specify the correct reader)
invalid_file_error = "The Protochips csv reader can't import the file, please"\
    " make sure, this is a valid Protochips log file."


def file_reader(filename, *args, **kwds):
    csv_file = ProtochipsCSV(filename)
    return _protochips_log_reader(csv_file)


def _protochips_log_reader(csv_file):
    csvs = []
    for key in csv_file.logged_quantity_name_list:
        try:
            csvs.append(csv_file.get_dictionary(key))
        except BaseException:
            raise IOError(invalid_file_error)
    return csvs


class ProtochipsCSV(object):

    def __init__(self, filename, ):
        self.filename = filename
        self._parse_header()
        self._read_data()

    def _parse_header(self):
        with open(self.filename, 'r') as f:
            s = f.readline()
            self.column_name = s.replace(', ', ',').replace('\n', '').split(',')
            if not self._is_protochips_csv_file():
                raise IOError(invalid_file_error)
            self._read_all_metadata_header(f)
        self.logged_quantity_name_list = self.column_name[2:]

    def _is_protochips_csv_file(self):
        # This check is not great, but it's better than nothing...
        if 'Time' in self.column_name and 'Notes' in self.column_name and len(
                self.column_name) >= 3:
            return True
        else:
            return False

    def get_dictionary(self, quantity):
        return {'data': self._data_dictionary[quantity],
                'axes': self._get_axes(),
                'metadata': self._get_metadata(quantity),
                'mapping': self._get_mapping(),
                'original_metadata': {'Protochips_header':
                                      self._get_original_metadata()}}

    def _get_original_metadata(self):
        d = {'Start time': self.start_datetime}
        d['Time units'] = self.time_units
        for quantity in self.logged_quantity_name_list:
            d['%s_units' % quantity] = self._parse_quantity_units(quantity)
        if self.user:
            d['User'] = self.user
        d['Calibration file path'] = self._parse_calibration_filepath()
        d['Time axis'] = self._get_metadata_time_axis()
        # Add the notes here, because there are not well formatted enough to
        # go in metadata
        d['Original notes'] = self._parse_notes()
        return d

    def _get_metadata(self, quantity):
        date, time = np.datetime_as_string(self.start_datetime).split('T')
        return {'General': {'original_filename': os.path.split(self.filename)[1],
                            'title': '%s (%s)' % (quantity,
                                                  self._parse_quantity_units(quantity)),
                            'date': date,
                            'time': time},
                "Signal": {'signal_type': '',
                           'quantity': self._parse_quantity(quantity)}}

    def _get_mapping(self):
        mapping = {
            "Protochips_header.Calibration file path": (
                "General.notes",
                self._parse_calibration_file_name),
            "Protochips_header.User": (
                "General.authors",
                None),
        }
        return mapping

    def _get_metadata_time_axis(self):
        return {'value': self.time_axis,
                'units': self.time_units}

    def _read_data(self):
        names = [name.replace(' ', '_') for name in self.column_name]
        # Necessary for numpy >= 1.14
        kwargs = {'encoding': 'latin1'} if np.__version__ >= LooseVersion("1.14") else {
        }
        data = np.genfromtxt(self.filename, delimiter=',', dtype=None,
                             names=names,
                             skip_header=self.header_last_line_number,
                             unpack=True, **kwargs)

        self._data_dictionary = dict()
        for i, name, name_dtype in zip(range(len(names)), self.column_name,
                                       names):
            if name == 'Notes':
                self.notes = data[name_dtype].astype(str)
            elif name == 'Time':
                self.time_axis = data[name_dtype]
            else:
                self._data_dictionary[name] = data[name_dtype]

    def _parse_notes(self):
        arr = np.vstack((self.time_axis, self.notes))
        return np.compress(arr[1] != '', arr, axis=1)

    def _parse_calibration_filepath(self):
         # for the gas cell, the calibration is saved in the notes colunm
        if hasattr(self, "calibration_file"):
            calibration_file = self.calibration_file
        else:
            calibration_file = "The calibration files names are saved in the"\
                " 'Original notes' array of the original metadata."
        return calibration_file

    def _parse_calibration_file_name(self, path):
        basename = os.path.basename(path)
        return "Calibration file name: %s" % basename.split('\\')[-1]

    def _get_axes(self):
        scale = np.diff(self.time_axis[1:-1]).mean()
        max_diff = np.diff(self.time_axis[1:-1]).max()
        units = 's'
        offset = 0
        if self.time_units == 'Milliseconds':
            scale /= 1000
            max_diff /= 1000
            # Once we support non-linear axis, don't forgot to update the
            # documentation of the protochips reader
            _logger.warning("The time axis is not linear, the time step is "
                            "thus extrapolated to {0} {1}. The maximal step in time step is {2} {1}".format(
                                scale, units, max_diff))
        else:
            warnings.warn("Time units not recognised, assuming second.")

        return [{'size': self.time_axis.shape[0],
                 'index_in_array': 0,
                 'name': 'Time',
                 'scale': scale,
                 'offset': offset,
                 'units': units,
                 'navigate': False
                 }]

    def _parse_quantity(self, quantity):
        quantity_name = quantity.split(' ')[-1]
        return '%s (%s)' % (quantity_name,
                            self._parse_quantity_units(quantity))

    def _parse_quantity_units(self, quantity):
        quantity = quantity.split(' ')[-1].lower()
        return self.__dict__['%s_units' % quantity]

    def _read_all_metadata_header(self, f):
        param, value = self._parse_metadata_header(f.readline())
        i = 2
        while 'User' not in param:  # user should be the last of the header
            if 'Calibration file' in param:
                self.calibration_file = value
            elif 'Date (yyyy.mm.dd)' in param:
                date = value
            elif 'Time (hh:mm:ss.ms)' in param:
                time = value
            else:
                attr_name = param.replace(' ', '_').lower()
                self.__dict__[attr_name] = value
            i += 1
            try:
                param, value = self._parse_metadata_header(f.readline())
            except ValueError:
                # when the last line of header does not contain 'User',
                # possibly some old file.
                self.user = None
                break
            except IndexError:
                _logger.warning("The metadata may not be parsed properly.")
                break
        else:
            self.user = value
        self.header_last_line_number = i
        self.start_datetime = np.datetime64(dt.strptime(date + time,
                                                        "%Y.%m.%d%H:%M:%S.%f"))

    def _parse_metadata_header(self, line):
        return line.replace(', ', ',').split(',')[1].split(' = ')
