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

# Copyright (c) 2012, 2014 Abhay Devasthale and Martin Raspaud

# Author(s):

#   Abhay Devasthale <abhay.devasthale@smhi.se>
#   Martin Raspaud <martin.raspaud@smhi.se>
#   Carlos Horn <carlos.horn@external.eumetsat.int>

# This program 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.

# This program 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 <http://www.gnu.org/licenses/>.

"""Read a gac file.

"""

import argparse
import logging
import os
import sys
import tarfile
from datetime import datetime
from functools import wraps

import pygac

if sys.version_info.major < 3:
    class FileNotFoundError(OSError):
        pass

logger = logging.getLogger("pygac")

verbosity2loglevel = {
    1: logging.INFO,
    2: logging.DEBUG
}

class MyFormatter(logging.Formatter):
    converter = datetime.fromtimestamp

    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = ct.strftime(datefmt)
        else:
            t = ct.strftime("%Y-%m-%d %H:%M:%S")
            s = "%s.%03d" % (t, record.msecs)
        return s


def str2scanline(string):
    """Convert string to scanline.

    Make sure, the scanline is not negative.

    Args:
        string (str): String to be converted

    Returns:
        int: Scanline
    """
    integer = int(string)
    if integer < 0:
        raise argparse.ArgumentTypeError('Scanlines must be >= 0')
    return integer


def validate_args(args):
    if args.end_line > 0 and args.start_line > args.end_line:
        raise ValueError('Start Scanline > End Scanline')
    if args.config and not os.path.isfile(args.config):
        raise FileNotFoundError(
            'The provided config file "%s" does not exist!'
            % args.config)
    if args.verbose > 0:
        # redirect the log messages to the standart output
        ch = logging.StreamHandler()
        formatter = MyFormatter(
            '[ %(levelname)s %(name)s %(asctime)s] %(message)s')
        ch.setFormatter(formatter)
        logger.addHandler(ch)
        # set the log-level depending on user verbosity
        verbosity = min(len(verbosity2loglevel), args.verbose)
        logger.setLevel(verbosity2loglevel[verbosity])


def tarfile_walker(path):
    """Yield all files from a tar archive.

        Args:
            path (str): Path to archive
        Yields:
            filename: name of the extracted file
            fileobj: corresponding file object
    """
    with tarfile.open(path) as archive:
        for tarinfo in archive:
            if tarinfo.isfile():
                filename = tarinfo.name
                fileobj = archive.extractfile(filename)
                yield filename, fileobj

def logged_trial(processor, debug=False):
    """Decorator to logs exceptions during processing."""
    @wraps(processor)
    def wrapper(filename, *args, **kwargs):
        try:
            processor(filename, *args, **kwargs)
        except Exception:
            logger.exception('Could not process "%s"' % str(filename))
            if debug:
                raise
    return wrapper


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description='Read, calibrate and navigate NOAA AVHRR GAC data')
    parser.add_argument('path', type=str, help='Path to GAC file(s) to be processed'
                        ' (Can be a file, directory or tar-archive)')
    parser.add_argument('start_line', type=str2scanline,
                        help='First scanline to be processed (0-based)')
    parser.add_argument('end_line', type=str2scanline,
                        help='Last scanline to be processed (0-based, '
                             'set to 0 for the last available scanline)')
    parser.add_argument('-c', '--config', type=str,
                        help='pygac config file to be used')
    parser.add_argument('-v', '--verbose', action='count', default=0,
                        help='Explain what is being done, frequent occurrence'
                             ' increases details')
    parser.add_argument('--debug', action='store_true',
                        help="If given, stop on the first exception, otherwise"
                        " log exception and continue with next file")
    parser.add_argument('--version', action='version',
                        version='pygac %s' % pygac.__version__)
    args = parser.parse_args()
    validate_args(args)
    if args.config:
        pygac.read_config_file(args.config)
    processor = logged_trial(pygac.process_file, debug=args.debug)
    path = args.path
    start_line = args.start_line
    end_line = args.end_line
    if os.path.isfile(path): 
        if tarfile.is_tarfile(path):
            logger.info('Open archive "%s"' % str(path))
            for filename, fileobj in tarfile_walker(path):
                processor(filename, start_line, end_line, fileobj=fileobj)
        else:
            filename = path
            # Raise exceptions in case of a single file.
            pygac.process_file(filename, start_line, end_line)
    elif os.path.isdir(path):
        logger.info('Open directory "%s"' % str(path))
        for basename in os.listdir(path):
            filename = os.path.join(path, basename)
            processor(filename, start_line, end_line)
    else:
        raise FileNotFoundError(
            'The provided path "%s" is neither a file nor a directory!' % args.path)
