#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""dasconv tool

This file is part of dastools.

dastools 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.

dastools 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 this program. If
not, see https://www.gnu.org/licenses/.

   :Copyright:
       2021 Helmholtz Centre Potsdam GFZ German Research Centre for Geosciences, Potsdam, Germany
   :License:
       GPLv3
   :Platform:
       Linux

.. moduleauthor:: Javier Quinteros <javier@gfz-potsdam.de>, GEOFON, GFZ Potsdam
"""

import argparse
import sys
import logging
import pprint
import datetime
from obspy import Trace
from .tdms import TDMS
from .tdms import NoData
from dastools import __version__
import dastools.archive as da
import inspect


def nslc(dataheader: dict) -> str:
    """Get a NSLC code from a dictionary with its components

    :param dataheader: Dictionary with components of a NSLC code
    :type dataheader: dict
    :return: NSLC code
    :rtype: str
    :raise KeyError: if keys 'network', 'station', 'location', or 'channel' are not present
    """
    return '%s.%s.%s.%s' % (dataheader['network'].upper(), dataheader['station'].upper(),
                            dataheader['location'].upper(), dataheader['channel'].upper())


def printmetadata(data):
    """Print the data in a pretty format

    Take into account the special case of a dictionary.
    """
    if isinstance(data, dict):
        pprint.pprint(data)
    else:
        print(data)


def str2date(dstr: str) -> datetime.datetime:
    """Transform a string to a datetime

    :param dstr: A datetime in ISO format.
    :type dstr: str
    :return: A datetime represented the converted input.
    :rtype: datetime.datetime
    :raise ValueError: if no integers are found as components of the string
    """
    # In case of empty string
    if (dstr is None) or (not len(dstr)):
        return None

    dateparts = dstr.replace('-', ' ').replace('T', ' ')
    dateparts = dateparts.replace(':', ' ').replace('.', ' ')
    dateparts = dateparts.replace('Z', '').split()
    # Consider the case in which just the first digits of microseconds
    # are given and complete with 0's to have 6 digits
    if len(dateparts) == 7:
        dateparts[6] = dateparts[6] + '0' * (6 - len(dateparts[6]))

    return datetime.datetime(*map(int, dateparts))


def main():
    # Inspect the archive.py module to list the Classes based on Archive
    dictarchive = dict()
    for name, obj in inspect.getmembers(da):
        if inspect.isclass(obj):
            if issubclass(obj, da.Archive) and name != 'Archive':
                dictarchive[name] = obj

    helparchive = 'Available options are [%s]' % ', '.join(dictarchive.keys())

    # Check verbosity in the output
    msg = 'Read, manipulate and convert seismic waveforms generated by a DAS system.'
    parser = argparse.ArgumentParser(description=msg)
    parser.add_argument('-l', '--loglevel',
                        help='Verbosity in the output.',
                        choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO',
                                 'DEBUG'],
                        default='WARNING')
    parser.add_argument('-d', '--directory',
                        help='Directory where files are located (default: ".")',
                        default='.')
    parser.add_argument('--start', '--starttime',
                        help='Start of the selected time window.\nFormat: 2019-02-01T00:01:02.123456Z',
                        default=None)
    parser.add_argument('--end', '--endtime',
                        help='End of the selected time window.\nFormat: 2019-02-01T00:01:02.123456Z',
                        default=None)
    parser.add_argument('--chstart', type=int,
                        help='First channel to export',
                        default=0)
    parser.add_argument('--chstop', type=int,
                        help='Last channel to export',
                        default=None)
    parser.add_argument('--chstep', type=int,
                        help='Step between channels in the selection',
                        default=1)
    parser.add_argument('--decimate', type=int, choices=[1, 5],
                        help='Factor by which the sampling rate is lowered by decimation.',
                        default=1)
    parser.add_argument('-N', '--network',
                        help='Network code to store in the miniseed header (default: "XX")',
                        default='XX')
    parser.add_argument('-C', '--channel',
                        help='Channel code to store in the miniseed header (default: "FSF")',
                        default='FSF')
    parser.add_argument('-o', '--outstruct', type=str, choices=dictarchive.keys(),
                        help=helparchive, default='StreamBased')
    parser.add_argument('--metadata', action='store_true', default=False,
                        help='Read and display the metadata from the TDMS files')
    parser.add_argument('-V', '--version', action='version', version='dasconv v%s' % __version__)
    parser.add_argument('filename',
                        help='Experiment to read and process. It is usually the first part of the filenames.')

    args = parser.parse_args()
    if args.metadata:
        logging.basicConfig(level=args.loglevel, stream=sys.stdout)
    else:
        logging.basicConfig(level=args.loglevel)

    logs = logging.getLogger('OpenFile')
    logs.setLevel(args.loglevel)

    dtstart = str2date(args.start)
    dtend = str2date(args.end)

    if dtend is not None and dtstart is not None and dtstart >= dtend:
        logs.error('End time is smaller than Start time.')
        return

    if len(args.network) != 2:
        logs.error('Network code must be two alphanumeric characters')
        return

    if len(args.channel) != 3:
        logs.error('Channel code must be three alphanumeric characters')
        return

    try:
        td = TDMS(args.filename, directory=args.directory, iterate='M' if args.metadata else 'D',
                  chstart=args.chstart, chstop=args.chstop, chstep=args.chstep, networkcode=args.network,
                  channelcode=args.channel, starttime=dtstart, endtime=dtend, decimate=args.decimate,
                  loglevel=args.loglevel)
    except NoData:
        logs.error('There seems to be no data under the experiment name provided')
        sys.exit(-1)

    # Selected archive structure
    # Archive files in current directory
    klass = dictarchive[args.outstruct]
    archive = klass(root='.', experiment=args.filename, strictcheck=False)

    expectedtimes = dict()

    with td:
        curstream = None

        for data in td:
            if args.metadata:
                printmetadata(data)
            else:
                logs.debug(data[1])

                if curstream != nslc(data[1]):
                    # Save the previous Stream completely
                    if curstream is not None:
                        archive.archive(tr0)
                        logs.info('Storing channel %s. Starttime: %s' % (curstream, data[1].get('starttime', None)))

                    # Update which stream is being processed
                    curstream = nslc(data[1])
                    # Create the Trace
                    tr0 = Trace(data=data[0], header=data[1])
                else:
                    # Check if there is a gap in the signal. If a gap is detected, flush now
                    if data[1]['starttime'] != expectedtimes[curstream]:
                        archive.archive(tr0)
                        tr0 = Trace(data=data[0], header=data[1])
                    else:
                        tr0 += Trace(data=data[0], header=data[1])

                # Update the datetime of the expected sample
                expectedtimes[curstream] = data[1]['starttime'] + data[1]['npts']/data[1]['sampling_rate']
        else:
            if not args.metadata:
                logs.info('Storing last part of channel %s' % curstream)
                try:
                    archive.archive(tr0)
                except KeyError:
                    archive.archive(tr0)

                except UnboundLocalError:
                    logs.error('Signal was not processed. Probably too short!')


if __name__ == '__main__':
    main()
