#!/usr/bin/env python3

# Copyright (C) 2018-2020 Simon Spöhel
#
# 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/>.

import logging
import argparse
import os
import datetime as dt
import glob
import sys
import hashlib
import configparser

from collections import OrderedDict
from sentinelsat import SentinelAPI, read_geojson, geojson_to_wkt

import sentinel

def file_exists(fname, stop=None):
    if not os.path.exists(fname):
        logging.warning('Cannot find file "%s".' % fname)
        if stop is not None:
            print('Exiting.')
            sys.exit(1)
        return 1
    return 0



def print_config( config ):
    return_list = list()
    for section in config.sections():
        return_list.append('[%s]' %section)
        for key, value in config.items(section):
            return_list.append('\t%s = %s' %(key, value))
    return '\n'.join(return_list)



def read_configs(conf_files, keys):
    '''Reads config files from a list of files.
    Adds path information for all options in "keys"-list.
    Returns a ConfigParser object.
    '''
    config = configparser.ConfigParser()
    for conf in conf_files:
        cfg = configparser.ConfigParser()
        cfg.read(conf)
        for section in cfg.sections():
            for key in keys:
                if cfg.has_option(section, key):
                    cfg[section][key] = '%s/%s' %(os.path.dirname(conf), cfg[section][key])
        config.read_dict( cfg )
    return config



def parse_global( config ):
    '''Parses the global part of a config file, creates a dictionary and
    returns it.'''

    if not config.has_section('global'):
        logging.critical('Found no section "global" in config file, exiting.')
        sys.exit(1)

    if not config.has_section('credentials'):
        logging.critical('Found no section "credentials" in config file, exiting.')
        sys.exit(1)

    settings = dict()

    try:
        settings['base_dest'] = config['global']['base_dest']
    except:
        logging.critical('could not parse "base_dest".')
        sys.exit(1)

    try:
        settings['platformname'] = config['global']['platformname']
    except:
        logging.critical('could not parse "platformname".')
        sys.exit(1)

    try:
        settings['username'] = config['credentials']['username']
    except:
        logging.critical('could not parse "username".')
        sys.exit(1)

    try:
        settings['password'] = config['credentials']['password']
    except:
        logging.critical('could not parse "password".')
        sys.exit(1)

    return settings



def parse_dlsettings(config):
    '''Parses a config file without the global part. Creates a dictionary
    with per region with a nested dictrionary for each region and returns
    it.'''

    if len(config.sections()) < 1:
        logging.critical('Found no sections in config file, exiting.')
        sys.exit(1)

    dlsettings = dict()

    logging.info('Parsing dlsettings.')

    for section in config.sections():

        try:
            date_start = dt.datetime.strptime(config[section]['date_start'], '%Y-%m-%d').date()
        except:
            logging.warning('could not parse "date_start", got %s' %config[section]['date_start'])

        try:
            date_end = dt.datetime.strptime(config[section]['date_end'], '%Y-%m-%d').date()
        except ValueError:
            if config[section]['date_end'] == 'today':
                date_end = dt.date.today()
            else:
                logging.warning('Could not parse "date_end", got "%s".' %config[section]['date_end'])
                return

        try:
            clouds_min = config[section].getint('clouds_min')
        except ValueError:
            logging.warning('Could not parse "clouds_min", got "%s".' %config[section]['clouds_min'])
            return

        try:
            clouds_max = config[section].getint('clouds_max')
        except ValueError:
            logging.warning('Could not parse "clouds_max", got "%s".' %config[section]['clouds_max'])
            return

        try:
            footprint = config[section]['footprint']
        except:
            logging.warning('Could not parse "footprint", got "%s".' %config[section]['footprint'])
            return

        try:
            product_type = config[section]['product_type']
        except:
            logging.warning('Could not parse "product_type", got "%s".' %config[section]['product_type'])
            return

        cfg = dict()
        cfg['date_start'] = date_start
        cfg['date_end'  ] = date_end
        cfg['clouds_min'] = clouds_min
        cfg['clouds_max'] = clouds_max
        cfg['footprint' ] = footprint
        cfg['product_type' ] = product_type

        dlsettings[section] = cfg
    return dlsettings



def load_ignores(ignore_file):
    '''Loads the ignores file and returns a list.'''

    if file_exists(ignore_file) is 0:
        logging.info('Loading ignore list.')

        with open(ignore_file) as f:
                ignores_string = f.read()

        ignores = ignores_string.split('\n')
        ignores = [x for x in ignores if len(x) > 0]
        ignores = [x for x in ignores if x[0] != '#']
        return ignores



def filter_products(df, ignorelist):
    '''Filters all items of an ignore list out of a dataframe
    and returns a list.'''

    df_filtered = df[ -df['filename'].isin(ignorelist)]
    return df_filtered.index.tolist()



parser = argparse.ArgumentParser(description='Download sentinel 2 data')

parser.add_argument('-a', '--auto',
                    help='use settings config files, implies -d',
                    action='store_true')

parser.add_argument('--debug',
                    choices=['debug','info','warning','critical'],
                    help='set the debug level.')

parser.add_argument('-d', '--download',
                    help='set this download the data',
                    action='store_true')

if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)

args = parser.parse_args()

if args.debug is not None:
    loglevel = args.debug
    args.debug = None
else:
    loglevel = 'WARNING'

numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise VelueError('Invalid log level: %s' % loglevel)
logging.basicConfig(stream=sys.stdout, level=numeric_level)

args_supplied = [x for x in vars(args).values() if x not in [None, False] ]


logging.info('New run begins at %s' %dt.datetime.now())

dumpconf_list = ['All configuration known to this run',
                 '===================================']
# only read conf files if arg.auto == True
if args.auto is True:
    logging.debug('auto was supplied, setting download = True.')
    args.download = True
    logging.debug('auto was supplied, parsing conf files.')
    # conf files
    settings_locations = [ '/etc/sentineldl', 'settings' ]

    # FIXME catch errors here.
    for item in settings_locations:
        if file_exists( item) == 0:
            settings_location = item
            break

    logging.info('Using settings location %s' %settings_location)
    conf_files = glob.glob('%s/**/*.conf' %settings_location, recursive=True)
    logging.debug('Parsing config files: %s.' %', '.join(conf_files))
    config = read_configs(conf_files, ['footprint','credentials_file'])
    dumpconf_list.append(print_config(config))

    cfg_settings = parse_global(config)
else:
    logging.critical('--auto is required at the moment')
    sys.exit(1)

settings = cfg_settings

#print('settings: ', settings)
dumpconf_list.append('\nsettings:')
dumpconf_list.append('%s' %settings)


# create dlsettings
dlsettings = config
del dlsettings['global']
del dlsettings['credentials']

dlconfig = parse_dlsettings(dlsettings)

dumpconf_list.append('\ndlconfig:')
dumpconf_list.append('%s' %dlconfig)

dumpconf_list.append('===================================')
logging.debug('\n'.join(dumpconf_list))

regions = list(dlconfig.keys())

# read ignores
ignore_files = glob.glob('%s/**/ignore.txt' %settings_location, recursive=True)

ignores = list()
for f in ignore_files:
    ignores.extend(load_ignores(f))

# remove duplicated entries in ignores
ignores = list(set(ignores))

sapi = SentinelAPI(settings['username'], settings['password'], show_progressbars=False)

for region_name in regions:
    cfg = dlconfig[region_name]

    footprint = geojson_to_wkt(read_geojson( cfg['footprint'] ))

    logging.info('Region: %s, product: %s' %(region_name, cfg['product_type']))

    metadata = sentinel.discover(
                           username = settings['username'],
                           password = settings['password'],
                           footprint = cfg['footprint'],
                           platformname = settings['platformname'],
                           producttype = cfg['product_type'],
                           dates = (cfg['date_start'], cfg['date_end']),
                           clouds = (cfg['clouds_min'], cfg['clouds_max']),
                           debug_save = False,
                           )

    if len(metadata) < 1:
        logging.info('Nothing to download.')
        continue

    metadata = metadata[~metadata.index.isin(ignores)]

    logging.info('%i products remaining for download.' %len(metadata.index) )

    if args.download != True:
        logging.warning('Skipping download. Use --download to avoid this message.')
        continue

    for index, row in metadata.iterrows():
        sentinel.download(
                username = settings['username'],
                password = settings['password'],
                base_dest= settings['base_dest'],
                index = index,
                row = row,
                debug_save=False)

# kate: indent-mode python; indent-pasted-text false; indent-width 4; replace-tabs true; tab-width 4;
