#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
datconv is a script intended to perform configurable comversion of file with data in one format to file with data in another format.

Script should be run using Python 2.7 or Python 3.x interpretter.
It also required installation of external modules: lxml, PyYAML.
For more information see README.md file distributed in source ball.

Both input and output files can be text or binary files.
However it is assumed that those files have following structure:
--- Some Header ---
Record 1
Record 2
....
Record N
--- Some Footer ---
There may be different types of records (i.e. every record has attribute called record type).
Each record may contain different number and kind of data (have different internal structure) even among records of the same type.

Program has modular architecture with following swichable compoments:
Reader - major obligatory component responsible for:
         reading input data (i.e. every reader class assumes certain input file format)
         driving entire data conversion process (i.e. main processing loop in implemented in this class)
         determine internal representation of header, records and footer (this strongly depands on reader and kind of input format).
Filter - optional compoment that is able to:
         filter data (i.e. do not pass certain records further - i.e. to writer for placing in output)
         change data (i.e. change on the fly contents of certain records)
         produce data (i.e. cause that certain records, maybe slightly modified, are being sent multiply times to writer)
         break conversion process (i.e. caused that conversion stop on certain record).
Writer - obligatory component responsible for:
         re-packing data from element-tree internal format to strings or objects.
Output Connector - obligatory component responsible for:
         writing data to destination storage.
Logger - all messages intended to be presented to user are being send (except few very initial error messages) to 
         Logger classes from Python standard library 'logging'.
         This script can use all comfiguration power of logging thru 'logging' package.

Except datconv script itself this package contain following modules/packages:
datconv - package that allows user to call data conversion from other Python program (using Datconv class)
datconv.readers - package that contain Reader classes for different widely used data file formats.
datconv.filters - package that contain some generic Filter clsaaes possible to use with different readers.
datconv.writers - package that contain Writrer classes for different widely used data file formats.
datconv.outconn.* - packages that contain output connectors for file and different databases.
For more detailed description of Reader, Filter, Writer and Output Connector interfaces see code and pydoc descriptions in _skeleton.py modules in readers, filters and writers sub-packages.
For more detailed description of concrete modules and classes provided with this package see their pydoc descriptions (refer Python documentation for information how to see it in Web Browser (e.g. pydoc -g works on Linux)).

The datconv script has following call syntax:
datconv [=]yaml_conf_file [--key1:val [--key2:val ...]] [arg1 [arg2 ...]]
where:
yaml_conf_file - is obligatory path to file in YAML format in which above described compoments are set up.
                 See file conf_template.yaml in this folder for more detailed desctiption of this file.
                 If there is '=' before yaml_conf_file then default configuration file is not used.
                 If yaml_conf_file is equal to 'def' than only default configuration file is used.
--key1:val - any number of arguments that add new settings or overwrite settings from yaml_conf_file.
             It works this way: let say that in yaml_conf_file we have:
             Writer:
                Module: writers.dcxml
                CArg: 
                    pretty:   true
             by invoking option --Writer:CArg:pretty:false we overwrite 'pretty' option of Writer.
             Note that in YAML file we have to have space after : that end the key, while in command line there are no spaces.
arg1 - any number of arguments (that do not begin with --).
       Those arguments will replace $1, $2, ... markers in yaml_conf_file according to their position in command line:
       i.e. $1 will be replaced by first argument that do not begin with --, etc.
or
datconv --default
which display default configuratiion file to standard output and exit.
or
datconv --version
which prints version number to standard output and exit.
or
datconv --help
which prints short usage information

The datconv script returns to shell:
    0 on sucess 
    1 on general error (exception)
    2 on invalid command parameters
    3 on user break (Ctrl-C)
"""

# Standard Python Libs
import sys, traceback
from os.path import basename, join, expanduser

# Libs installed using pip
import yaml

# Datconv generic modules
from datconv import Datconv, Logger, _DatconvMergeConf


########################################################################
from datconv.version import *
__author__  = datconv_author
__status__  = datconv_status
__version__ = datconv_version
__date__    = datconv_date


############################################################################
if __name__ == "__main__":
    try:
        ####################################################################
        ## Command line arguments
        script_name = basename(sys.argv[0])
        if len(sys.argv) < 2 or sys.argv[1] == '--help':
            if len(sys.argv) < 2:
                sys.stderr.writelines("ERROR: Wrong command arguments - too few parameters:\n")
            sys.stderr.writelines(
"""Usage1: {0} [=]conf_file [--key1:val [--key2:val ...]] [arg1 [arg2 ...]]
Usage2: {0} def [--key1:val [--key2:val ...]] [arg1 [arg2 ...]]
Usage3: {0} --default[-raw]
Usage4: {0} --version
Usage5: {0} --help
Complete documentation: http://datconv.readthedocs.io/en/latest/api.html#the-datconv-program-reference
""".format(script_name))
            sys.exit(2)
            
        if sys.argv[1] == '--version':
            sys.stdout.write('datconv version: %s\n' % datconv_version)
            try:
                from datconv.gwextract.version import datconv_igt_version
                sys.stdout.write('datconv-igt version: %s\n' % datconv_igt_version)
            except ImportError:
                pass
            sys.exit(0)

        arg_keys = list()
        arg_num = 1
        def_text = ''
        conf_text = ''
        conf_name = sys.argv[1]
        if conf_name[0] == '=':
            conf_name = conf_name[1:]
        else:
            def_name = join(expanduser('~'), '.datconv_def.yml')
            try:
                def_text = open(def_name).read()
            except IOError:
                pass
        if sys.argv[1].startswith('--default'):
            sys.stdout.write('Default configuration file: %s\n' % def_name)
            if def_text == '':
                sys.stdout.write('Does not exist.\n')
            else:
                if sys.argv[1] == '--default-raw':
                    sys.stdout.write('\n' + def_text + '\n')
                else:
                    try:
                        def_conf = yaml.load(def_text)
                        sys.stdout.write('\n' + yaml.dump(def_conf) + '\n')
                    except yaml.scanner.ScannerError:
                        sys.stdout.write('Is not correct:\n')
                        sys.stdout.write('\n' + def_text + '\n')
            sys.exit(0)
        
        if sys.argv[1] != 'def':
            try:
                conf_text = open(conf_name).read()
            except IOError:
                traceback.print_exc()
                sys.exit(2)
        
        for i in range(2, len(sys.argv)):
            if sys.argv[i].startswith('--'):
                kv = sys.argv[i][2:].split(':')
                if len(kv) < 2:
                    sys.stderr.writelines(
"""ERROR: Wrong command arguments; argument: %s should have the form: --key:val
Where key is collon-separated compoud key: e.g. Writer:CArg:pretty
""" % sys.argv[i])
                    sys.exit(2)
                arg_keys.append(kv)
            else:
                def_text  =  def_text.replace('$' + str(arg_num), sys.argv[i])
                conf_text = conf_text.replace('$' + str(arg_num), sys.argv[i])
                arg_num = arg_num + 1
        
        conf = dict()
        if len(conf_text) > 0:
            conf = yaml.load(conf_text)
        if len(def_text) > 0:
            _DatconvMergeConf(conf, yaml.load(def_text))
            
        for kv in arg_keys:
            arg_key = yaml.load('key: ' + kv[-1])
            ac = conf
            for key in kv[:-2]:
                if key not in ac:
                    ac[key] = dict()
                ac = ac[key]
            ac[kv[-2]] = arg_key['key']

        ####################################################################
        ## Run process
        if 'DefLogLevel' not in conf:
            conf['DefLogLevel'] = 'INFO'
        dc = Datconv()
        sys.exit(dc.Run(conf))
    
    except Exception:
        sys.stderr.write('Exception was thrown during %s execusion\n' % script_name)
        traceback.print_exc()
        sys.exit(1)

