#!/usr/bin/env python

# -*- coding: utf-8 -*-
"""

 _____ _    ______ _____ _____   _   _ _ _
/  __ \ |   | ___ \_   _/  ___| | | | | | |
| /  \/ |__ | |_/ / | | \ `--.  | | | | | |_ _ __ ___  _ __
| |   | '_ \|    /  | |  `--. \ | | | | | __| '__/ _ \| '_ \
| \__/\ | | | |\ \ _| |_/\__/ / | |_| | | |_| | | (_) | | | |
 \____/_| |_\_| \_|\___/\____/   \___/|_|\__|_|  \___/|_| |_|



font generated by:
http://patorjk.com/software/taag/#p=display&f=Doom&t=ChRIS%20Ultron

ChRIS_SM

'ChRIS_SM' is the 'ChRIS Simulated Machine' or 'ChRIS Simple Machine'
or ... :-) -- basically it is a simplified infrastructure for
developing / testing the API between the front end view and various
back-end services.

Essentially, 'ChRIS_SM' simulates the API behaviour on a synthetic
instance of ChRIS consisting of a single user (with password) and
a few feeds containing different data, views, results, notes, etc.


"""

from __future__ import print_function

import  abc

import  error
import  message
import  crun
import  hashlib
import  sys
import  os
import  argparse
import  json

class ChRIS_client(object):
    """Simple class that handles comms between a back end ChRIS_SM

    """

    #
    # Class member variables -- if declared here are shared
    # across all instances of this class
    #
    _dictErr = {
        'shellFailure'   : {
            'action'        : 'attempting to run shell job, ',
            'error'         : 'a failure was detected.',
            'exitCode'      : 11}
    }

    def log(self, *args):
        '''
        get/set the internal pipeline log message object.

        Caller can further manipulate the log object with object-specific
        calls.
        '''
        if len(args):
            self._log = args[0]
        else:
            return self._log

    def name(self, *args):
        '''
        get/set the descriptive name text of this object.
        '''
        if len(args):
            self.__name = args[0]
        else:
            return self.__name


    callCounter     = 0

    def __init__(self, **kwargs):
        """Constructor
        """
        self._log                       = message.Message()
        self._log._b_syslog             = True
        self.__name                     = "ChRIS_client"

        self._str_executable            = ""

        self._str_token                 = ""
        self._str_seed                  = ""

        self._b_formatAllJSON           = False
        self.l_call                     = []

        self._shell                     = crun.crun()
        self._shell.waitForChild(True)
        self._shell.echoStdOut(False)
        self._shell.echoStdErr(False)
        self._shell.echo(False)

        for key, val in kwargs.iteritems():
            if key == "simulatedMachine":   self._str_executable    = val
            if key == "formatAllJSON":      self._b_formatAllJSON   = val

    @abc.abstractmethod
    def __call__(self, *args, **kwargs):
        """Each sub class must define this method!
        """

    def stdout(self):
        return self._shell.stdout()

    def generateToken(self, jsonRetString):
        """Determines the token to use in the next API call

        Args:
            jsonRetString:  The JSON payload returned from the call to the web service

        Returns:
            str_token:  The token to use for the next API call.

        :param jsonRetString:
        :return:
        """
        jdata   = json.loads(jsonRetString)
        for key,val in jdata.iteritems():
            if key == 'token':  self._str_token = val
            if key == 'seed':   self._str_token = val
        str_hashInput   = "%s%s" % (self._str_token, self._str_seed)
        return m.md5(str_hash).hexdigest()

    def JSONdump_all(self):
        """
        Prints a fully conformant JSON type object around the l_history
        :param al_history: history of calls
        :return:
        """
        print("{")
        for i in range(0, len(self.l_call)):
            if not i:
                print(" \"call_%03d\": " % i, end="")
            else:
                print(",\"call_%03d\": " % i, end="")
            print(json.dumps(self.l_call[i]))
        print("}")

class ChRIS_client_REST(ChRIS_client):
    """A REST client"""

    def __init__(self, **kwargs):
        ChRIS_client.__init__(self, **kwargs)

    def __call__(self, *args, **kwargs):
        """Entry point mimicking the external call to the web service
        """

        ChRIS_client.callCounter += 1

        try:
            job = self._shell("%s --REST --APIcall %s" % (self._str_executable, args[0]))
        except:
            error.fatal(self, 'shellFailure',
                        '\nExecuting:\n\t%s\nstdout:\n-->\t%s\nstderr:\n-->%s' %
                        (self._shell._str_cmd, self._shell.stdout(), self._shell.stderr()))
        if self._shell._exitCode:
            error.fatal(self, 'shellFailure', '\nExit code failure:\n\t%s\n%s\n%s' %
                        (self._shell._exitCode, self._shell._str_cmd, self._shell.stderr()))
        return(json.loads(self.stdout()))

class ChRIS_client_RPC(ChRIS_client):
    """An RPC-specialized client
    """

    def __init__(self, **kwargs):
        ChRIS_client.__init__(self, **kwargs)
        self._str_stateFile     = ""
        for key, val in kwargs.iteritems():
            if key == "stateFile":      self._str_stateFile     = val

    def __call__(self, *args, **kwargs):
        """Entry point mimicking the external call to the web service
        """

        ChRIS_client.callCounter += 1

        try:
            job = self._shell("%s --RPC --APIcall %s --stateFile %s" % (self._str_executable, args[0], self._str_stateFile))
        except:
            error.fatal(self, 'shellFailure',
                        '\nExecuting:\n\t%s\nstdout:\n-->\t%s\nstderr:\n-->%s' %
                        (self._shell._str_cmd, self._shell.stdout(), self._shell.stderr()))
        if self._shell._exitCode:
            error.fatal(self, 'shellFailure', '\nExit code failure:\n\t%s\n%s\n%s' %
                        (self._shell._exitCode, self._shell._str_cmd, self._shell.stderr()))
        return(json.loads(self.stdout()))

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

            %s [--REST | --RPC --stateFile <stateFile>] [--formatAllJSON]


    ''' % scriptName

    description =  '''
    DESCRIPTION

        `%s' shows client side connection to the ChRIS_SM API service.

    ARGS

       --REST
       Use a REST API paradigm.

       --RPC --stateFile <stateFile>
       Use an RPC API paradigm. In this case, a <stateFile> is also
       required to track session history.

       API calls are logged to <stateFile> and replayed back when
       ChRIS_SM is instantiated. In this manner the machine state
       can be rebuilt. The current <APIcall> is appended to the
       <stateFile>.

       --formatAllJSON
       If specified, format the entire set of output responses as a "meta"
       JSON string. Useful for downstream parsing with "|python -m json.tool"

    EXAMPLES


    ''' % (scriptName)
    if ab_shortOnly:
        return shortSynopsis
    else:
        return shortSynopsis + description


if __name__ == "__main__":
    """Simulated session interacting with the ChRIS_SM

    This module simulates an external client communicating with the
    back end.

    """

    if len(sys.argv) == 1:
        print(synopsis())
        sys.exit(1)

    parser      = argparse.ArgumentParser(description = synopsis(True))
    parser.add_argument(
        '--REST',
        action  =   'store_true',
        dest    =   'b_REST',
        help    =   'Specify REST API paradigm.',
        default =   False
    )
    parser.add_argument(
        '--RPC',
        action  =   'store_true',
        dest    =   'b_RPC',
        help    =   'Specify RPC API paradigm.',
        default =   False
    )
    parser.add_argument(
        '-s', '--stateFile',
        help    =   "The <stateFile> keeps track of ChRIS state.",
        dest    =   'str_stateFileName',
        action  =   'store',
        default =   "<void>"
    )
    parser.add_argument(
        '-f', '--formatAllJSON',
        help    =   "If specified, wrap *all* output in a JSON object, useful for downsteam parsing.",
        dest    =   'formatAllJSON',
        action  =   'store_true',
        default =   False
    )

    args        = parser.parse_args()
    m           = hashlib
    l_call      = []

    if args.b_RPC:
        API         = ChRIS_client_RPC(     simulatedMachine = './ChRIS_SM.py',
                                            stateFile        = args.str_stateFileName,
                                            formatAllJSON    = args.formatAllJSON)
        # First login...
        API.l_call.append(
            API("\"http://chris_service?returnstore=d&object=chris&method=login&parameters=user='chris',passwd='chris1234'&clearSessionFile=1\"")
        )

        # Now do something...

        # Get a list of feeds for the homepage
        API.l_call.append(
            API("\"http://chris_service?object=chris.homePage&method=feeds_organize&parameters=schema='default'&auth=user='chris',hash='dabcdef1234'\"")
        )

        # Get a list of plugins that are valid at the scope of the homepage
        API.l_call.append(
            API("\"http://chris_service?object=chris.homePage.plugin&method=getList&auth=user='chris',hash='dabcdef1234'\"")
        )

        # Choose a specific plugin
        API.l_call.append(
            API("\"http://chris_service?object=chris.homePage.plugin&method=set&parameters='file_browser'&auth=user='chris',hash='dabcdef1234'\"")
        )

        # Run it...
        API.l_call.append(
            API("\"http://chris_service?object=chris.homePage.plugin&method=run&auth=user='chris',hash='dabcdef1234'\"")
        )

        # This creates a new feed ... the client needs to request a new list of Feeds and render it.

        # Get details about specific feeds
        API.l_call.append(
            API("\"http://chris_service?object=chris.homePage&method=feed_getFromObjectName&parameters='Feed-3',returnAsDict=True&auth=user='chris',hash='dabcdef1234'\"")
        )

        API.l_call.append(
            API("\"http://chris_service?object=chris.homePage&method=feed_getFromObjectName&parameters='Feed-2',returnAsDict=True&auth=user='chris',hash='dabcdef1234'\"")
        )

        # Finally, log out...
        API.l_call.append(
            API("\"http://chris_service?returnstore=d&object=chris&method=logout&parameters=user='chris'\"")
        )

        API.JSONdump_all()

    if args.b_REST:
        API         = ChRIS_client_REST(    simulatedMachine = './ChRIS_SM.py',
                                            formatAllJSON    = args.formatAllJSON)
        # First login...
        API.l_call.append(
            API("\"http://chris_service/v1/login?auth=user=chris,passwd=chris1234\"")
        )

        # Now do something...

        # Get a list of feeds for the homepage
        API.l_call.append(
            API("\"http://chris_service/v1/Feeds?auth=user=chris,hash=123456\"")
        )

        # Get a specific feed...
        API.l_call.append(
            API("\"http://chris_service/v1/Feeds/NAME_Feed-1?auth=user=chris,hash=123456\"")
        )

        # Finally, log out...
        API.l_call.append(
            API("\"http://chris_service/v1/logout?auth=user=chris\"", jwrap="end")
        )

        API.JSONdump_all()
