#!/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>]            \\
                                [--filterFor <innerFilterCommaList>]    \\
                                [--onCUBE <CUBEjsonDetails>]            \\
                                [--onCUBEaddress <address>]             \\
                                [--onCUBEport <port>]                   \\
                                [--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

        [--filterFor <innerFilterCommaList>]
        A further filtering of a set of hits. Typically a pattern of `--for`
        and `--using` can return a list space of hits. The `--filterFor`
        searches the values of this returned list space for further hits.

        For example, searching `--for flag,name --across parameters` will
        return the space of CLI flags and names for a given plugin
        `--using plugin_id=<N>`. This hit return space can be further
        filtered for a given CLI flag with a `--filterFor ' --in_name'`.

        Note that the filter hit, `' --in_name'` is quoted with an explicit
        leading space to protect it from interpretation by the module itself.

        Multiple filters can be specified with comma concatenation, i.e.
        `--filterFor ' --in_name,--out_name'`

        [--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``.

        [--onCUBEport  <port>]
        Instead of passing a complete JSON object as with ``--onCUBE``, in
        some cases the port  of the ``CUBE`` instance, often in conjunction
        with [--onCUBEaddress <address>] needs to be set. This flag is a mechanism
        to specifically set the port 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:

        $ chrispl-search --for id,status,plugin_name --using plugin_name=mri \\
                         --across plugininstances

        (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

    * To get a list of files resources associated with the above plugin INSTANCE id

        $ chrispl-search --for file_resource --using plugin_inst_id=9       \\
                         --across links                                     \\
                         --onCUBEaddress localhost --onCUBEport 8333

        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/157/PatientF.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/156/PatientE.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/155/PatientD.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/154/PatientC.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/153/PatientB.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/152/PatientA.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/162/jobStatusSummary.json
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/161/jobStatus.json
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/160/input.meta.json
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/159/ex-covid.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/158/ex-covid-ct.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/151/0006.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/150/0005.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/149/0004.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/148/0003.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/147/0002.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/146/0001.dcm
        (searchSubstr:plugin_inst_id=9)  file_resource http://localhost:8333/api/v1/files/145/0000.dcm

    * To get a list of CLI flags, internal name, and help string associated
      with plugin id 8
        $ chrispl-search --for flag,name,help --using plugin_id=8           \\
                         --across parameters --onCUBEaddress localhost --onCUBEport 8333

        (searchSubstr:plugin_id=8)  flag --subjectDir            name subjectDir             help directory (relative to <inputDir>) of subjects to process
        (searchSubstr:plugin_id=8)  flag --in_name               name iname                  help name of the input (raw) file to process (default: brain.mgz)
        (searchSubstr:plugin_id=8)  flag --out_name              name oname                  help name of the output segmented file
        (searchSubstr:plugin_id=8)  flag --order                 name order                  help interpolation order
        (searchSubstr:plugin_id=8)  flag --subject               name subject                help subject(s) to process. This expression is globbed.
        (searchSubstr:plugin_id=8)  flag --log                   name logfile                help name of logfile (default: deep-seg.log)
        (searchSubstr:plugin_id=8)  flag --network_sagittal_path name network_sagittal_path  help path to pre-trained sagittal network weights
        (searchSubstr:plugin_id=8)  flag --network_coronal_path  name network_coronal_path   help path to pre-trained coronal network weights
        (searchSubstr:plugin_id=8)  flag --network_axial_path    name network_axial_path     help path to pre-trained axial network weights
        (searchSubstr:plugin_id=8)  flag --clean                 name cleanup                help if specified, clean up segmentation
        (searchSubstr:plugin_id=8)  flag --no_cuda               name no_cuda                help if specified, do not use GPU
        (searchSubstr:plugin_id=8)  flag --batch_size            name batch_size             help batch size for inference (default: 8
        (searchSubstr:plugin_id=8)  flag --simple_run            name simple_run             help simplified run: only analyze one subject
        (searchSubstr:plugin_id=8)  flag --run_parallel          name run_parallel           help if specified, allows for execute on multiple GPUs
        (searchSubstr:plugin_id=8)  flag --copyInputImage        name copyInputImage         help if specified, copy input file to output dir.

    * Determine the internal value to POST to CUBE for a given plugin CLI flag:
      (note this is an *exact* flag / string search -- thus flag filters must
       have leading '--' where appropriate):

        $ ./chrispl-search --for flag,name --using plugin_id=8              \\
                           --across parameters                              \\
                           --onCUBEaddress localhost --onCUBEport 8333      \\
                           --filterFor " --in_name,--out_name"                                                                         ─╯

        (searchSubstr:plugin_id=8)  flag --in_name               name iname
        (searchSubstr:plugin_id=8)  flag --out_name              name oname

    * 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     = "2.2.6"
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(
    '--onCUBEport',
    help    = 'the port of a CUBE instance',
    action  = 'store',
    dest    = 'str_CUBEport',
    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(
    '--filterFor',
    help    = 'fine tune the list of hits for a value substring',
    action  = 'store',
    dest    = 'str_filterFor',
    default = '',
)
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
    t_key       :   tuple   = ()
    str_line    :   str     = ""
    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='')
                str_line    = ""
                for hit in target:
                    t_key     = tuple(hit)
                    str_line += '%15s %-30s' % (hit[t_key[0]], hit[t_key[1]])
                query.dp.qprint(str_line, syslog=False)
                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)
