import argparse
import sys

from lodel import logger
from lodel.exceptions import *

##@defgroup lodel2_script Administration scripts
#@ingroup lodel2_plugins

##@package lodel.plugin.script
#@brief Lodel2 utility for writting administration scripts
#@ingroup lodel2_plugins
#@ingroup lodel2_script

##@brief Stores registered scripts
#@todo store it in MetaLodelScript
__registered_scripts = dict()

##@brief LodelScript metaclass that allows to "catch" child class
#declaration
#@ingroup lodel2_script
#@ingroup lodel2_plugins
#
#Automatic action registration on child class declaration
class MetaLodelScript(type):
    
    def __init__(self, name, bases, attrs):
        #Here we can store all child classes of LodelScript
        super().__init__(name, bases, attrs)
        if len(bases) == 1 and bases[0] == object:
            return

        self.__register_script(name)
        #_action initialization
        if self._action is None:
            logger.warning("%s._action is None. Trying to use class name as \
action identifier" % name)
            self._action = name
        self._action = self._action.lower()
        if self._description is None:
            self._description = self._default_description()
        self._parser = argparse.ArgumentParser(
            prog = self._prog_name(),
            description = self._description)
        self.argparser_config(self._parser)
            
    
    ##@brief Handles script registration
    #@note Script list is maitained in 
    #lodel.plugin.admin_script.__registered_scripts
    def __register_script(self, name):
        if self._action is None:
            logger.warning("%s._action is None. Trying to use class name as \
action identifier" % name)
            self._action = name
        self._action = self._action.lower()
        script_registration(self._action, self)

    def __str__(self):
        return '%s : %s' % (self._action, self._description)


##@brief Class designed to facilitate custom script writting
#@ingroup lodel2_plugins
#@ingroup lodel2_script
class LodelScript(object, metaclass=MetaLodelScript):
    
    ##@brief A string to identify the action
    _action = None
    ##@brief Script descripiton (argparse argument)
    _description = None
    ##@brief argparse.ArgumentParser instance
    _parser = None
    
    ##@brief No instanciation
    def __init__(self):
        raise NotImplementedError("Static class")
    
    ##@brief Virtual method. Designed to initialize arguement parser.
    #@param argparser ArgumentParser : Child class argument parser instance
    #@return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it \
    #works by reference)
    @classmethod
    def argparser_config(cls, parser):
        raise LodelScriptError("LodelScript.argparser_config() is a pure \
virtual method! MUST be implemented by ALL child classes")
    
    ##@brief Virtual method. Run the script
    #@return None or an integer that will be the script return code
    @classmethod
    def run(cls, args):
        raise LodelScriptError("LodelScript.run() is a pure virtual method. \
MUST be implemented by ALL child classes")
    
    ##@brief Called by main_run() to execute a script.
    #
    #Handles argument parsing and then call LodelScript.run()
    @classmethod
    def _run(cls):
        args = cls._parser.parse_args()
        return cls.run(args)

    ##@brief Append action name to the prog name
    #@note See argparse.ArgumentParser() prog argument
    @classmethod
    def _prog_name(cls):
        return '%s %s' % (sys.argv[0], cls._action)
    
    ##@brief Return the default description for an action
    @classmethod
    def _default_description(cls):
        return "Lodel2 script : %s" % cls._action
    
    @classmethod
    def help_exit(cls,msg = None, return_code = 1, exit_after = True):
        if not (msg is None):
            print(msg, file=sys.stderr)
        cls._parser.print_help()
        if exit_after:
            exit(1)

def script_registration(action_name, cls):
    __registered_scripts[action_name] = cls
    logger.info("New script registered : %s" % action_name)

##@brief Return a list containing all available actions
def _available_actions():
    return [ act for act in __registered_scripts ]

##@brief Returns default runner argument parser
def _default_parser():

    action_list = _available_actions()
    if len(action_list) > 0:
        action_list = ', '.join(sorted(action_list))
    else:
        action_list = 'NO SCRIPT FOUND !'

    parser = argparse.ArgumentParser(description = "Lodel2 script runner")
    parser.add_argument('-L', '--list-actions', action='store_true',
        default=False, help="List available actions")
    parser.add_argument('action', metavar="ACTION", type=str,
        help="One of the following actions : %s" % action_list, nargs='?')
    parser.add_argument('option', metavar="OPTIONS", type=str, nargs='*',
        help="Action options. Use %s ACTION -h to have help on a specific \
action" % sys.argv[0])
    return parser

##@brief Main function of lodel_admin.py script
#
#This function take care to run the good plugins and clean sys.argv from
#action name before running script
#
#@return DO NOT RETURN BUT exit() ONCE SCRIPT EXECUTED !!
def main_run():
    default_parser = _default_parser()
    if len(sys.argv) == 1:
        default_parser.print_help()
        exit(1)
    #preparing sys.argv (deleting action)
    action = sys.argv[1].lower()
    if action not in __registered_scripts:
        #Trying to parse argument with default parser
        args = default_parser.parse_args()
        if args.list_actions:
            print("Available actions :")
            for sname in sorted(__registered_scripts.keys()):
                print("\t- %s" % __registered_scripts[sname])
            exit(0)

        print("Unknow action '%s'\n" % action, file=sys.stderr)
        default_parser.print_help()
        exit(1)
    #OK action is known, preparing argv to pass it to the action script
    del(sys.argv[1])
    script = __registered_scripts[action]
    ret = script._run()
    ret = 0 if ret is None else ret
    exit(ret)

##@page lodel2_script_doc Lodel2 scripting
#@ingroup lodel2_script
#
#@section lodel2_script_adm Lodel2 instance administration scripts
#
#Lodel2 provides instance administration operation using Makefiles or 
#lodel_admin.py script ( see @ref lodel2_instance_admin ).
#
#The lodel_admin.py script take as first option an action. Each action
#correspond to a sub-script with it's own options etc. To get a list
#of all available action run <code>python3 lodel_admin.py -L</code>.
#
#@section lodel2_script_action lodel_admin.py actions
#
#Action implementation is done by class inheritance. To create a new action
#write a @ref lodel.plugin.scripts.LodelScript "LodelScript" derived class (
#see @ref lodel.plugin.core_scripts "core_scripts.py" file as example )
#
#@subsection lodel2_script_inheritance LodelScript inheritance
#
#In order to implement properly a new action you have to write a new 
#@ref lodel.plugin.scripts.LodelScript "LodelScript" derivated class.
#Some methods and attributes are mandatory to write a fully functionnal
#derivated class. Here is a list :
#
#- mandatory methods
# - @ref plugin.scripts.LodelScript.argparser_config() "argparser_config()" :
#(classmethod) initialize argparse.Parser
# - @ref plugin.scripts.LodelScript.run() "run()" : (classmethod) contains the
#code that runs to perform the action
#- mandatory attributes
# - @ref plugin.scripts.LodelScript::_action "_action" : (class attribute)
#stores action name
# - @ref plugin.scripts.LodelScript::_description "_description" : (class 
#attribute) sotres a short action description
#
#@note On script's action registration : once child class is written you only
#need to import it to trigger script's action registration (see
#@ref plugin.scripts.MetaLodelScript )
#