#!/usr/bin/env python3
"""
Run the GRAVITY recipes on all data in the current directory
output the results in reduced/ and reduced/calibrated/

"""
from gravi_reduce import (log, ERROR, WARNING, NOTICE, set_verbose_type)
from gravi_reduce import gravi_esorex as ge
from gravi_reduce import gravi_esorex_help as geh
import time
import os
import glob
import argparse
from argparse import Namespace

log = log.Log().log

usage = """
description:
  Run the GRAVITY recipes on all data in the current directory
  output the results in reduced/ and reduced/calibrated/
"""

examples = """
examples:
  List all recipes:
  run_gravi_reduce.py -l

  Show the manual of a recipe
  run_gravi_reduce.py -m gravity_vis

  Show the options of a recipe
  run_gravi_reduce.py -lo gravity_vis

  Run at night, to reduce all incoming files:
  run_gravi_reduce.py --loop=TRUE

  Recalibrate only:
  run_gravi_reduce.py --dark=FALSE --p2vm=FALSE --vis=FALSE --tf=TRUE --viscal=TRUE

  Compute the DISP_MODEL:
  run_gravi_reduce.py --dark=TRUE --p2vm=TRUE --wavelamp=TRUE --disp=TRUE

  Pass option to recipe:
  run_gravi_reduce  recipe_name.option=value ...
"""

#
# Implement options
#

parser = argparse.ArgumentParser(description=usage, epilog=examples,conflict_handler="resolve",
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
TrueFalse = ['TRUE', 'FALSE']
TrueFalseOverwrite = ['TRUE', 'FALSE', 'OVERWRITE']

parser.add_argument("--loop", dest="loop", default='FALSE', choices=TrueFalse,
                    help="Run in loop every 10 seconds [FALSE]")

parser.add_argument("--commoncalib-dir", dest="commoncalibdir",
                    default="../../common_calibration",
                    help="Add this directory in the search path for calibration files,"
                    "in addition of current and reduced/   [../../common_calibration]")

parser.add_argument("--reduced-dir", dest="reduceddir", default="reduced",
                    help="Directory for products [reduced/]")

parser.add_argument("--dark", dest="dark", default='TRUE', choices=TrueFalse,
                    help="Reduce the standalone DARKs [TRUE]")

parser.add_argument("--wave", dest="wave", default='FALSE', choices=TrueFalse,
                    help="Reduce the standalone WAVEs [FALSE]")

parser.add_argument("--p2vm", dest="p2vm", default='TRUE', choices=TrueFalse,
                    help="Reduce the DARK,FLAT,P2VM,WAVE sequences [TRUE]")

parser.add_argument("--vis", dest="vis", default='TRUE',
                    choices=TrueFalseOverwrite,
                    help="Reduce the object into visibility [TRUE]")

parser.add_argument("--wavelamp", dest="wavelamp", default='FALSE',
                    choices=TrueFalse,
                    help="Reduce the WAVELAMP [FALSE]")

parser.add_argument("--disp", dest="disp", default='FALSE', choices=TrueFalse,
                    help="Reduce the DISP [FALSE]")

parser.add_argument("--piezotf", dest="piezotf", default='FALSE',
                    choices=TrueFalse,
                    help="Reduce the PIEZOTF [FALSE]")

parser.add_argument("--tf", dest="tf", default='FALSE', choices=TrueFalse,
                    help="Compute transfer function [FALSE]")

parser.add_argument("--viscal", dest="viscal", default='FALSE',
                    choices=TrueFalseOverwrite,
                    help="Calibrate vis from tf [FALSE]")

parser.add_argument("--average-sky", dest="averagesky", default='FALSE',
                    choices=TrueFalse,
                    help="Average over all available Skys [FALSE]")

parser.add_argument("--show-notice", dest="shownotice", default='FALSE',
                    choices=TrueFalse,
                    help="Always shows the NOTICE messages [FALSE]")

parser.add_argument("--ncores", dest="ncores", default=1, type=int,
                    help="If larger 1 it will start mutiothreading (experimental!) [1]")

#
# esorex recipe options (minimal support)
#

options_esorex = [['esorex.msg-level', 'info'],
                  ['esorex.time', 'TRUE'],
                  ['esorex.mem-check', 'TRUE'],
                  ['esorex.recipe-dir', None],
                  ['gravity_vis.astro-file', 'TRUE'],
		  ['gravity_disp.p2vmreduced-file', 'TRUE']]

#options = [['gravity_dark.bias-method', None],
#           ['gravity_dark.bias-subtracted-file', None],
#           ['gravity_p2vm.profile-mode', None],
#           ['gravity_p2vm.profile-width', None],
#           ['gravity_p2vm.extra-pixel-ft', None],
#           ['gravity_p2vm.interp-3pixels', None],
#           ['gravity_p2vm.force-wave-ft-equal', None],
#           ['gravity_p2vm.phase-calibration', None],
#           ['gravity_p2vm.bias-method', None],
#           ['gravity_vis.p2vmreduced-file', None],
#           ['gravity_vis.astro-file', 'FALSE'],
#           ['gravity_vis.bias-method', None],
#           ['gravity_vis.bias-subtracted-file', None],
#           ['gravity_vis.chi2r-threshold', None],
#           ['gravity_vis.chi2r-sigma', None],
#           ['gravity_vis.use-fiber-dxy', None],
#           ['gravity_vis.use-met-rtc', None],
#           ['gravity_vis.nsmooth-snr-ft', None],
#           ['gravity_vis.snr-min-ft', None],
#           ['gravity_vis.state-min-ft', None],
#           ['gravity_vis.tracking-min-sc', None],
#           ['gravity_vis.vfactor-min-sc', None],
#           ['gravity_vis.use-met-zero', None],
#           ['gravity_vis.imaging-ref-met', None],
#           ['gravity_vis.opd-pupil-max-sc', None],
#           ['gravity_vis.opd-pupil-stddev-max-sc', None],
#           ['gravity_vis.max-frame', None],
#           ['gravity_vis.vis-correction-sc', None],
#           ['gravity_vis.phase-ref-sc', None],
#           ['gravity_vis.output-phase-sc', None],
#           ['gravity_vis.flat-flux', None],
#           ['gravity_vis.reduce-acq-cam', 'FALSE'],
#           ['gravity_vis.color-wave-correction', None],
#           ['gravity_vis.preproc-file', None],
#           ['gravity_vis.spectrum-file', None],
#           ['gravity_disp.p2vmreduced-file', None],
#           ['gravity_disp.vis-file', None],
#           ['gravity_vis.smooth-faint', None],
#           ['gravity_vis.preswitch-delay', None],
#           ['gravity_vis.postswitch-delay', None]]

ge.implement_recipe_options(parser, options_esorex)
# the options can be only parse in one place
# conflict_handler='error' 'resolve'
# ge.implement_recipe_options(parser, options)

parser.add_argument("--list-recipes", "-l", dest="listrecipes",
                    help="List recipes", action="store_true")
parser.add_argument("--man", "-m", nargs=1, metavar=('RECIPE'),
                    help="Show recipe manual")
parser.add_argument("--list-options", "-lo", dest="listoptions",
                    nargs=1, metavar=('RECIPE'), help="Show recipe options")

#
# Main program
#

if __name__ == "__main__":

    # Parse arguments
    # argoptions = parser.parse_args()
    argoptions, recipes_args = parser.parse_known_args()

    if argoptions.listrecipes:
        print(geh.retrieve_esorex_recipes())
        exit()

    if argoptions.man:
        if len(argoptions.man) == 0:
            parser.error("the following arguments are required: recipe")
            parser.print_help()
        recipe = argoptions.man[0]
        print(geh.retrieve_esorex_man(recipe))
        exit()

    if argoptions.listoptions:
        print('\n')
        if len(argoptions.listoptions) == 0:
            parser.error("the following arguments are required: recipe")
            parser.print_help()
        recipe = argoptions.listoptions[0]
        geh.retrieve_recipe_options(recipe)
        exit()

    # Propagate esorex verbose to python logging system
    msg_level = vars(argoptions)['esorex.msg-level']
    if msg_level == 'error':
        if argoptions.shownotice == 'TRUE':
            set_verbose_type(ERROR+NOTICE)
        else:
            set_verbose_type(ERROR)
    elif msg_level == 'warning' and argoptions.shownotice == 'FALSE':
        set_verbose_type(ERROR+WARNING)
    else:
        set_verbose_type(ERROR+WARNING+NOTICE)

    # accept all recipes options
    if recipes_args is not None and len(recipes_args) > 0:
        a = geh.implement_recipe_options(parser, recipes_args)
        argoptions = Namespace(**vars(argoptions), **a)  # | recipes_args

    ##
    # Load directories
    commoncalibdir = argoptions.commoncalibdir
    reduceddir = argoptions.reduceddir
    calibrateddir = reduceddir+"/calibrated"

    # Load the current directory, the reduced and the common
    log("Reducing data in directory "+os.getcwd(), 1, NOTICE)
    raw = []
    ge.add_gravity(raw, glob.glob('*GRAVI*fits'))

    reduced = []
    ge.add_gravity(reduced, glob.glob(reduceddir+'/*GRAVI*fits'))

    common = []
    ge.add_gravity(common, glob.glob(commoncalibdir+'/*GRAVI*fits'))

    # Loop every 10s during 24h maximum
    dtime = 10
    if argoptions.loop == 'TRUE':
        Nloop = int(24*3600./dtime)
    else:
        Nloop = 1

    ##
    # Start loop
    dhash = [0]
    loop_counter = 0
    while loop_counter < Nloop:

        # Check if new file
        if not ge.new_file_in_dir(".", dhash):
            log("wait %d'' for new file" % dtime, 1, NOTICE)
            time.sleep(dtime)
            loop_counter += 1
            continue

        # Load files (will only load new ones)
        ge.add_gravity(raw, glob.glob('GRAVI*fits'))

        # Reduce PIEZOTF_RAW if files are in the directory
        if argoptions.piezotf == 'TRUE':
            ge.reduce_piezotf_raw(raw, reduceddir, options=argoptions)

        # Reduce the DARK_RAW and P2VM_RAW
        if argoptions.dark == 'TRUE':
            ge.reduce_dark_raw(raw, reduceddir, options=argoptions)

        # Reduce the WAVE_RAW
        if argoptions.wave == 'TRUE':
            ge.add_gravity(reduced, glob.glob(reduceddir+'/GRAVI*fits'))
            ge.reduce_wave_raw(raw, reduced+common, reduceddir, options=argoptions)

        # Reduce the P2VM_RAW
        if argoptions.p2vm == 'TRUE':
            ge.reduce_p2vm_raw(raw, common, reduceddir, options=argoptions)

        # Reduce the WAVELAMP_RAW
        if argoptions.wavelamp == 'TRUE':
            ge.add_gravity(reduced, glob.glob(reduceddir+'/GRAVI*fits'))
            ge.reduce_wavelamp_raw(raw, reduced+common, reduceddir, options=argoptions)

        # Reduce the DISP_RAW
        if argoptions.disp == 'TRUE':
            ge.add_gravity(reduced, glob.glob(reduceddir+'/GRAVI*fits'))
            ge.reduce_disp_raw(raw, reduced+common, reduceddir, options=argoptions)

        # Reduce the OBJECT
        if argoptions.vis == 'TRUE' or argoptions.vis == 'OVERWRITE':
            ge.add_gravity(reduced, glob.glob(reduceddir+'/GRAVI*fits'))
            ge.reduce_object_raw(raw, reduced+common, reduceddir,
                                 options=argoptions,
                                 overwrite=True if argoptions.vis == 'OVERWRITE' else False,
                                 averagesky=True if argoptions.averagesky == 'TRUE' else False)
            if argoptions.ncores > 1:
                ge.reduce_object_raw(raw, reduced+common, reduceddir,
                                     options=argoptions, onlycreate=True,
                                     overwrite=True,
                                     averagesky=True if argoptions.averagesky == 'TRUE' else False)

        # Compute the TF
        if argoptions.tf == 'TRUE':
            ge.add_gravity(reduced, glob.glob(reduceddir+'/GRAVI*.fits'))
            ge.compute_tf(reduced, reduced+common, calibrateddir,
                          options=argoptions)

        # Calibrate the visibilities (overwrite since new TF may have been created)
        if argoptions.viscal == 'TRUE' or argoptions.viscal == 'OVERWRITE':
            ge.add_gravity(reduced, glob.glob(reduceddir+'/GRAVI*.fits'))
            calibrated = []
            ge.add_gravity(calibrated, glob.glob(calibrateddir+'/GRAVI*.fits'))
            ge.calibrate_vis(reduced, calibrated, calibrateddir,
                             overwrite=True if argoptions.viscal == 'OVERWRITE' else False)

        loop_counter += 1
