#!/usr/bin/env python3

import  logging
logging.disable(logging.CRITICAL)

import  os
import  sys
import  json
import  socket
import  requests

import  pudb
import  pfmisc

# pfstorage local dependencies
from    pfmisc._colors      import  Colors
from    pfmisc.debug        import  debug
from    pfmisc.C_snode      import  *
from    pfstate             import  S

from    argparse            import RawTextHelpFormatter
from    argparse            import ArgumentParser

sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..'))

from    chrisclient         import search

str_desc        = """

    NAME

        chrispl-search

    PREREQUISITES

        The python `pfmisc` and `pfstate` modules:

            pip install -U pfmisc pfstate

    SYNOPSIS

        chrispl-search          --for <someField(s)>                    \\
                                --using <someTemplateDesc>              \\
                                [--across <metaSearchSpace>]            \\
                                [--onCUBE <CUBEjsonDetails>]            \\
                                [--onCUBEaddress <address>]             \\
                                [--returnKeyList]                       \\
                                [--version]                             \\
                                [--man]                                 \\
                                [--jsonReturn]                          \\
                                [--syslogPrepend]                       \\
                                [--verbosity <level>]

    ARGS

        --for <someField(s)>
        A comma separated list of fields to return from the plugin search.
        Results are typically printed in a "table", with each row containing
        the value for each of the `for` fields.

        Typical examples:

            --for name
            --for name,id

        --using <someTemplateDesc>
        The search condition/pattern to apply. This is typically some
        value relevant to the plugin description as referenced with the
        CUBE API.

        The <someTemplateDesc> can be a compound string that is comma
        separated. For instance 'type=ds,name=pfdo' will search for all
        plugins that have 'pfdo' in their name and 'ds' as their type.

        Typical examples:

            --using name=pl-fshack
            --using name=dicom,type=ds

        [--across <metaSearchSpace>]
        An optional qualifier that defines a "meta" search space. Valid
        qualifiers are 'plugins', 'plugininstances' and 'files'. Specifying a
        given <metaSearchSpace> does have contextual implications since
        the --for and --using arguments can be <metaSearchSpace> dependent.

        Typical examples:

            --for fname --using plugin_inst_id=9 --across files

        will return the field 'fname' for all files created by plugin
        instance id=9. The <metaSeachSpace> identifiers are contextual. Thus,
        searching given <across> fields might have specific <using> and
        <for> requirements.

        Note that the default <metaSearchSpace> is `plugins`.

        [--onCUBE <CUBEjsonDetails>]
        A JSON string that defines detail perinent to CUBE. If not passed,
        will use defaults. If passed, make sure to contain all the following:

            --onCUBE '
            {
                "protocol":     "http",
                "port":         "8000",
                "address":      "%HOSTIP",
                "user":         "chris",
                "password":     "chris1234",
            }
            '

        The ``%HOSTIP`` is a special meta token that the script will replace
        with the actual IP of the host system. This may be Linux specific,
        so beware!

        [--onCUBEaddress <address>]
        Instead of passing a complete JSON object as with ``--onCUBE``, in
        some cases only the address (IP or name) of the ``CUBE`` instance
        needs to be set. This flag is a mechanism to only set the address
        of the target ``CUBE``.

        [--returnKeyList]
        If true, return in the JSON hit structure an element that contains
        all the key names for the search return.

        [--version]
        Print the version and exit.

        [--jsonReturn]
        If specified, print the full JSON return from the API call and
        various class methods that apply processing to the API call.

        [--syslogPrepend]
        If specified, prepend pseudo (colorized) syslog info to output
        print calls.

        [--verbosity <level>]
        Apply a verbosity to the output.


    DESCRIPTION

        ``chrispl-search`` provides for a simple CLI mechanism to search
        a CUBE instance plugin space for various detail. Given, for example,
        a plugin name, return the plugin ID, or given a plugin type, return
        all names.

        The search can also consider plugin instances, which allow for search
        on end status, instance IDs, etc.

        Additional "metaSpaces" to search across include the files created
        by plugin instances.

        The primary purpose of this script is a helper component in creating
        autonomous feeds to a ChRIS instance from some stand-alone script,
        although it is quite well suited as a CLI mechanism to query for
        information on various plugins in a CUBE instantiation.

    EXAMPLES

    * List by name all the DS plugins in a CUBE instance:

        $ chrispl-search --for name --using type=ds
        (searchSubstr:type=ds)      name pl-pfdo_med2img
        (searchSubstr:type=ds)      name pl-pfdo_mgz2img
        (searchSubstr:type=ds)      name pl-mgz2lut_report
        (searchSubstr:type=ds)      name pl-z2labelmap
        (searchSubstr:type=ds)      name pl-freesurfer_pp
        (searchSubstr:type=ds)      name pl-fastsurfer_inference
        (searchSubstr:type=ds)      name pl-fshack
        (searchSubstr:type=ds)      name pl-mpcs
        (searchSubstr:type=ds)      name pl-pfdicom_tagsub
        (searchSubstr:type=ds)      name pl-pfdicom_tagextract
        (searchSubstr:type=ds)      name pl-s3push
        (searchSubstr:type=ds)      name pl-dsdircopy
        (searchSubstr:type=ds)      name pl-s3retrieve
        (searchSubstr:type=ds)      name pl-simpledsapp

    * List by name all DS plugins that also have 'dicom' in their name:

        $ chrispl-search --for name --using type=ds,name=dicom
        (searchSubstr:type=ds,name=dicom)      name pl-pfdicom_tagsub
        (searchSubstr:type=ds,name=dicom)      name pl-pfdicom_tagextract

    * Find the ID of the `pl-fshack` plugin:

        $ chrispl-search --for id --using name=pl-fshack
        (name=pl-fshack)        id      10

    * List all the plugins (by passing an empty string to ``name``) by
      name, id, and plugin type:

        $ chrispl-search --for name,id,type --using name=''
        (searchSubstr:name=)      name pl-pfdo_med2img            id 17   type ds
        (searchSubstr:name=)      name pl-pfdo_mgz2img            id 16   type ds
        (searchSubstr:name=)      name pl-mgz2lut_report          id 15   type ds
        (searchSubstr:name=)      name pl-z2labelmap              id 13   type ds
        (searchSubstr:name=)      name pl-freesurfer_pp           id 12   type ds
        (searchSubstr:name=)      name pl-fastsurfer_inference    id 11   type ds
        (searchSubstr:name=)      name pl-fshack                  id 10   type ds
        (searchSubstr:name=)      name pl-mpcs                    id 9    type ds
        (searchSubstr:name=)      name pl-pfdicom_tagsub          id 8    type ds
        (searchSubstr:name=)      name pl-pfdicom_tagextract      id 7    type ds
        (searchSubstr:name=)      name pl-s3push                  id 6    type ds
        (searchSubstr:name=)      name pl-dsdircopy               id 5    type ds
        (searchSubstr:name=)      name pl-s3retrieve              id 3    type ds
        (searchSubstr:name=)      name pl-simpledsapp             id 2    type ds
        (searchSubstr:name=)      name pl-lungct                  id 18   type fs
        (searchSubstr:name=)      name pl-mri10yr06mo01da_normal  id 14   type fs
        (searchSubstr:name=)      name pl-dircopy                 id 4    type fs
        (searchSubstr:name=)      name pl-simplefsapp             id 1    type fs

    * Search for the plugin INSTANCE id, status, and full name of plugins
      that have a substring of "mri" in their names (note the ``--searchURL``):

        $ chrispl-search --for id,status,plugin_name --using plugin_name=mri --searchURL plugins/instances
        (searchSubstr:plugin_name=mri)  id 8    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 7    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 6    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 5    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 4    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 3    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 2    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal
        (searchSubstr:plugin_name=mri)  id 1    status finishedSuccessfully  plugin_name pl-mri10yr06mo01da_normal

    * Find the status of a specific plugin INSTANCE id (in this case, id=9):

        $ chrispl-search --for status --using id=9 --across plugininstances
        (searchSubstr:id=9)    status finishedSuccessfully

    * To get a list of files created by the above plugin INSTANCE id

        $ chrispl-search --for fname --using plugin_inst_id=9 --across files
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/PatientF.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/PatientE.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/PatientD.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/PatientC.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/PatientB.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/PatientA.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/jobStatusSummary.json
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/jobStatus.json
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/input.meta.json
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/ex-covid.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/ex-covid-ct.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0006.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0005.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0004.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0003.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0002.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0001.dcm
        (searchSubstr:plugin_inst_id=9)     fname chris/feed_9/pl-lungct_9/data/0000.dcm


    * Pass a ``--jsonReturn`` for full JSON return information, including
      a list of keys to use in a given search space.

"""

# Determine the hostIP
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.2.8"
str_name        = "chrispl-search"
parser          = ArgumentParser(
                    description     = str_desc,
                    formatter_class = RawTextHelpFormatter
)

parser.add_argument(
    '--onCUBE',
    help    = 'A JSON string defining the details of a CUBE instance',
    action  = 'store',
    dest    = 'str_CUBE',
    default = '',
)
parser.add_argument(
    '--onCUBEaddress',
    help    = 'the address of a CUBE instance',
    action  = 'store',
    dest    = 'str_CUBEaddress',
    default = '',
)
parser.add_argument(
    '--version',
    help    = 'if specified, print verion',
    action  = 'store_true',
    dest    = 'b_version',
    default = False,
)
parser.add_argument(
    '--man', '-x',
    help    = 'if specified, show help and exit',
    action  = 'store_true',
    dest    = 'b_man',
    default = False,
)
parser.add_argument(
    '--syslogPrepend',
    help    = 'if specified, prepend syslog info to output',
    action  = 'store_true',
    dest    = 'b_syslog',
    default = False,
)
parser.add_argument(
    '--jsonReturn',
    help    = 'if specified, return results as JSON',
    action  = 'store_true',
    dest    = 'b_json',
    default = False,
)
parser.add_argument(
    '--for',
    help    = 'property for which to search',
    action  = 'store',
    dest    = 'str_for',
    default = '',
)
parser.add_argument(
    '--using',
    help    = 'search template in <key>=<value>[,..] form',
    action  = 'store',
    dest    = 'str_using',
    default = '',
)
parser.add_argument(
    '--across',
    help    = 'a metaspace across which to search (files, instances, etc)',
    action  = 'store',
    dest    = 'str_across',
    default = 'plugins',
)
parser.add_argument(
    '--returnKeyList',
    help    = 'return the list of keys in the search space',
    action  = 'store_true',
    dest    = 'b_returnKeyList',
    default = False,
)
parser.add_argument(
    '--verbosity',
    help    = 'the system verbosity',
    action  = 'store',
    dest    = 'verbosity',
    default = 1,
)

args        = parser.parse_args()

def preprocessing_do(*args):
    """
    Some quick preprocessing (check for --man or --version flags)
    """
    d_args  = vars(args[0])
    if d_args['b_man']:
        print(str_desc)
        sys.exit(0)

    if d_args['b_version']:
        print(str_version)
        sys.exit(0)

def postprocessing_do(query, d_result):
    """
    Final postprocessing housekeeping.
    """
    retCode     :   int      = 1
    if query.d_args['b_json']:
        query.dp.qprint(json.dumps(d_result, indent = 4))
        if len(d_result['target']):
            retCode = 0
    else:
        if d_result['status']:
            for target in d_result['target']:
                query.dp.qprint('(searchSubstr:%s)' % query.d_args['str_using'], end='')
                for hit in target:
                    query.dp.qprint('%10s %-30s' % (hit['name'], hit['value']),
                                    end='', syslog=False)
                query.dp.qprint('')
                retCode = 0
    return retCode

def main(*args):
    """
    The main method of the script, when called directly from the CLI
    """
    retCode     : int   = 1
    d_meta      : dict  = {
        'version':  str_version,
        'name':     str_name,
        'desc':     str_desc,
        'defIP':    str_defIP
    }

    preprocessing_do(*args)

    query       = search.PluginSearch(d_meta, args[0])
    d_result    = query.do()
    retCode     = postprocessing_do(query, d_result)

    sys.exit(retCode)

if __name__ == "__main__":
    main(args)
