#!/usr/bin/env python3
#
# (c) 2017 Fetal-Neonatal Neuroimaging & Developmental Science Center
#                   Boston Children's Hospital
#
#              http://childrenshospital.org/FNNDSC/
#                        dev@babyMRI.org
#

import sys, os, pudb, socket, json
sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..'))

from    pfstate             import S
from    argparse            import RawTextHelpFormatter
from    argparse            import ArgumentParser
from    pfmisc._colors      import Colors
from    pfmisc.C_snode      import C_snode


str_defIP   = [l for l in (
                [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2]
                if not ip.startswith("127.")][:1],
                    [[(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close())
                for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][0][0]

str_version = "1.1.1.4"
str_desc    = Colors.CYAN + """

        __     _         _
       / _|   | |       | |
 _ __ | |_ ___| |_  __ _| |_  ___
| '_ \|  _/ __| __|/ _` | __|/ _ \\
| |_) | | \__ \ |_| (_| | |_|  __/
| .__/|_| |___/\__|\__,_|\__|\___|
| |
|_|

                            Path-File-state

           An stateful class -- part of the pf* family.

                              -- version """ + \
             Colors.YELLOW + str_version + Colors.CYAN + """ --

    ``pfstate`` is a class designed to, at a *class* level, maintain state.
    Thus, it is a class that has state information at the object/class level
    and not as member variables.

    The class was designed to overcome some inherent limitations in using the
    python HTTPServer module, namely:

        * The server module is constructed with a parameter denoting a class
          to handle calls/storage. Thus, from the constructor to the server
          it is impossible to set internal values of the handler.

        * each HTTP call to the server instantiated a new handler instance,
          hence any state information that might exist in the handler is
          reset at each call.

    Moreover, the class was also created as an alternate to using global
    variables. In essence, this class object is a container analogue for
    "global" variables and provides an arguably cleaner mechanism for
    encapsulating some form of global state.

    This class object is designed to be used as a module and its contained
    state object 'S' can be added to an existing class and subclassed as
    necessary. See the __main__ method calling.

""" + Colors.NO_COLOUR

def synopsis(ab_shortOnly = False):
    scriptName = os.path.basename(sys.argv[0])
    shortSynopsis =  '''
    NAME

	    pfstate

        - pf* state object

    SYNOPSIS

            pfstate                                                 \\
                [--verbosity <level>]                               \\
                [--configFileLoad <file>]                           \\
                [--configFileSave <file>]                           \\
                [--test]                                            \\
                [-x|--desc]                                         \\
                [-y|--synopsis]                                     \\
                [--msg <JSON_formatted>]

    BRIEF EXAMPLE

            pfstate                                                 \\
                --verbosity 1


    '''

    description =  '''
    DESCRIPTION

    ``pfstate`` provides an encapsulated container for "global" type
    variables.

    Internally, data is organized initially as a dictionary which is
    re-cast into an SNode tree. To use, this class should be subclassed,
    and its class member variable 'S' set to the needs of the calling
    program/system.

    ARGS

        [--msg '<JSON_formatted>']
        An optional JSON formatted string exemplifying how to get and
        set internal variables.

        --msg '
        {
            "action": "internalctl",
            "meta": {
                        "var":     "/",
                        "get":      "value"
                    }
        }'

        --msg '
        {   "action": "internalctl",
            "meta": {
                        "var":     "/service/megalodon",
                        "set":     {
                            "compute": {
                                "addr": "10.20.1.71:5010",
                                "baseURLpath": "api/v1/cmd/",
                                "status": "undefined"
                            },
                            "data": {
                                "addr": "10.20.1.71:5055",
                                "baseURLpath": "api/v1/cmd/",
                                "status": "undefined"
                            }
                        }
                    }
        }'

        [--configFileLoad <file>]
        Load configuration information from the JSON formatted <file>.

        [--configFileSave <file>]
        Save configuration information to the JSON formatted <file>.

        [-x|--desc]
        Provide an overview help page.

        [-y|--synopsis]
        Provide a synopsis help summary.

        [--version]
        Print internal version number and exit.

        [--debugToDir <dir>]
        A directory to contain various debugging output -- these are typically
        JSON object strings capturing internal state. If empty string (default)
        then no debugging outputs are captured/generated. If specified, then
        ``pfcon`` will check for dir existence and attempt to create if
        needed.

        [-v|--verbosity <level>]
        Set the verbosity level. "0" typically means no/minimal output. Allows for
        more fine tuned output control as opposed to '--quiet' that effectively
        silences everything.

    EXAMPLES

    pfstate                                                \\
        --msg '
            {  "action": "internalctl",
                "meta": {
                            "var":     "/",
                            "get":      "value"
                        }
            }'
    '''
    if ab_shortOnly:
        return shortSynopsis
    else:
        return shortSynopsis + description

parser  = ArgumentParser(description = str_desc, formatter_class = RawTextHelpFormatter)

parser.add_argument(
    '--msg',
    action  = 'store',
    dest    = 'msg',
    default = '',
    help    = 'Message payload for internalctl control.'
)
parser.add_argument(
    '--version',
    help    = 'if specified, print version number',
    dest    = 'b_version',
    action  = 'store_true',
    default = False
)
parser.add_argument(
    '--test',
    help    = 'if specified, perform internal tests',
    dest    = 'b_test',
    action  = 'store_true',
    default = False
)
parser.add_argument(
    '--configFileLoad',
    help    = 'a file containing configuration information',
    dest    = 'str_configFileLoad',
    action  = 'store',
    default = ''
)
parser.add_argument(
    '--configFileSave',
    help    = 'a file to store configuration information',
    dest    = 'str_configFileSave',
    action  = 'store',
    default = ''
)
parser.add_argument(
    '--debugToDir',
    help    = 'a destination directory to contain debugging info',
    dest    = 'str_debugToDir',
    action  = 'store',
    default = ''
)
parser.add_argument(
    "-v", "--verbosity",
    help    = "verbosity level for app",
    dest    = 'verbosity',
    default = "1")
parser.add_argument(
    "-x", "--desc",
    help    = "long synopsis",
    dest    = 'desc',
    action  = 'store_true',
    default = False
)
parser.add_argument(
    "-y", "--synopsis",
    help    = "short synopsis",
    dest    = 'synopsis',
    action  = 'store_true',
    default = False
)

args            = parser.parse_args()

if args.desc or args.synopsis:
    print(str_desc)
    if args.desc:
        str_help     = synopsis(False)
    if args.synopsis:
        str_help     = synopsis(True)
    print(str_help)
    sys.exit(1)

if args.b_version:
    print("Version: %s" % str_version)
    sys.exit(1)

# pudb.set_trace()

# Create a derived class with additional problem-specific state

class D(S):
    """
    A derived class with problem-specific state
    """

    def __init__(self, *args, **kwargs):
        """
        Constructor
        """
        if not S.b_init:
            S.__init__(self, *args, **kwargs)
            d_specific  = \
                {
                    'specificState': {
                        'desc':         'Additional state information',
                        'theAnswer':    42,
                        'theQuestion':  'What do you get if you multiple six by nine',
                        'foundBy':      'Arthur Dent'
                    },
                    'earthState': {
                        'current':      'Destroyed',
                        'reason':       'Hyper space bypass',
                        'survivors': {
                            'humans':   ['Arthur Dent', 'Ford Prefect', 'Trillian'],
                            'dolphins': 'Most of them'
                        }
                    }
                }
            S.d_state.update(d_specific)
            S.T.initFromDict(S.d_state)
            S.b_init    = True
        self.dp.qprint(
            Colors.YELLOW + "\n\t\tInternal data tree:",
            level   = 1,
            syslog  = False)
        self.dp.qprint(
            C_snode.str_blockIndent(str(S.T), 3, 8),
            level   = 1,
            syslog  = False)

state   = D(
    version     = str_version,
    desc        = str_desc,
    args        = vars(args)
)

if len(args.msg):
    d_control = state.internalctl_process(request = json.loads(args.msg))
    print(
        json.dumps(
            d_control,
            indent = 4
        )
    )

