#!/usr/bin/env python
#                                                            _
# PACS ToolKit findscu (and possible movescu) Wrapper
#
# (c) 2016-2019 Fetal-Neonatal Neuroimaging & Developmental Science Center
#                   Boston Children's Hospital
#
#              http://childrenshospital.org/FNNDSC/
#                        dev@babyMRI.org
#

import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '../'))


from    argparse            import RawTextHelpFormatter
from    argparse            import ArgumentParser
from    terminaltables      import SingleTable
from    pfmisc._colors      import Colors
import  json
import  pypx
import  socket
import  pudb

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_name    = "px-find"
str_version = "3.0.0"
str_desc    = Colors.CYAN + """

                                   __  _             _ 
                                  / _|(_)           | |
             _ __  __  __ ______ | |_  _  _ __    __| |
            | '_ \ \ \/ /|______||  _|| || '_ \  / _` |
            | |_) | >  <         | |  | || | | || (_| |
            | .__/ /_/\_\        |_|  |_||_| |_| \__,_|
            | |                                        
            |_|                                        


                        PACS ToolKit Wrapper
                            Query / Find

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

    ``px-find`` is a module / script that provides functionality for
    performing a PACS Query operation.

    ``px-find`` is tested against both Orthanc as well as some
    commercial PACS offerings.

""" + Colors.NO_COLOUR

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

	    %s

        - PACS find (query)

    SYNOPSIS

            _script mode_:
            px-find
                [--db <dblogbasepath>]                              \\
                [--aet <AETitle>]                                   \\
                [--aec <CalledAETitle>]                             \\
                [--serverIP <PACSserverIP>]                         \\
                [--serverPort <PACSserverPort>]                     \\
                [--findscu <findscuAbsolutePath>]                   \\
                [--movescu <movescuAbsolutePath>]                   \\
                [--PatientID <MRN>]                                 \\
                [--PatientName <ExactPatientName>]                  \\
                [--PatientSex <M_or_F>]                             \\
                [--StudyDate <YYYY/MM/DD>]                          \\
                [--ModalitiesInStudy <Modalities>]                  \\
                [--Modality <ModalityToQuery>]                      \\
                [--PerformedStationAETitle <StationTitle>]          \\
                [--StudyDescription <studyDescription>]             \\
                [--SeriesDescription <seriesDescription>]           \\
                [--StudyInstanceUID <studyInstanceUID>]             \\
                [--SeriesInstanceUID <seriesInstanceUID>]           \\
                [--ProtocolName <ProtocolName>]                     \\
                [--AcquisitionProtocolName <APName>]                \\
                [--AcquisitionProtocolDescription <APDescription>]  \\
                [--AcquisitionProtocolName <APName>]                \\
                [--then <actionlist>] [--withFeedBack]              \\
                [--json]                                            \\
                [--waitForUserTerminate]                            \\
                [-x|--desc]                                         \\
                [-y|--synopsis]                                     \\
                [--version]                                         \\
                [--debugToDir <dir>]                                \\
                [--verbosity <level>]

    ''' % scriptName + Colors.NO_COLOUR

    description = Colors.LIGHT_GREEN + '''

    DESCRIPTION

        ``px-find`` is the main entry point to executing a PACS
        Query. Optional additional behaviours include requesting
        a Retrieve and Status -- these latter two behaviours operate
        on the series space that the find / query returns.

        Typically, the results of the ``px-find`` are piped to
        a ``px-report`` script for report generation.

    ARGS

        [--db <dblogbasepath>]
        A path to the base directory of the DB contents/files that track
        received files. This is typically the <logDir> of the `px-repack`
        process that repacks incoming DICOM files. Specifying this path
        allows `px-find` to access db tables needed to track the number
        of DICOM files that are received in the case of a 'retrieve'
        event.

        [--aet <AETitle>]
        The AETitle of *this* entity.

        [--aec <CalledAETitle>]
        The called AETitle of *this* entity. Needed for some PACS systems.

        [--serverIP <PACSserverIP>]
        The IP of the PACS server.

        [--serverPort <PACSserverPort>]
        The port associated with the PACS server.

        [--executable <findscuAbsolutePath>]
        The absolute location of the 'findscu' executable.

        [--AccessionNumber <AccessionNumber>]
        The AccessionNumber to Q/R.

        [--PatientID <MRN>]
        The patient ID on which to Query.

        [--PatientName <ExactPatientName>]
        The patient name on which to Query.

        [--PatientSex <M_or_F>]
        The patient sex (largely irrelevant since open ended searches are
        not supported by most PACS servers).

        [--StudyDate <YYYY/MM/DD>]
        Filter results by <studyDate>.

        [--ModalitiesInStudy <Modalities>]
        Filter by modalities in study.

        [--Modality <Modality>]
        The modality to filter.

        [--PerformedStationAETitle <StationTitle>]
        Filter by station ID.

        [--StudyDescription <studyDescription>]
        Filter by study description.

        [--SeriesDescription <seriesDescription>]
        Filter by series description.

        [--StudyInstanceUID <studyInstanceUID>]
        Filter by study instance UID.

        [--SeriesInstanceUID <seriesInstanceUID>]
        Filter by series instance UID.

        [--ProtocolName <ProtocolName>]
        Filter by protocol name.

        [--AcquisitionProtocolDescription <APDescription>]
        Filter by acquisition protocol description.

        [--AcquisitionProtocolName <APName>]
        Filter by acquisition protocol name.

        [--then <actionlist>]
        If specified, define an operation to do "next", i.e. after the find
        has completed. The <actionlist> is a comma separate list of verbs,
        including but not necessarily limited to

            * "retrieve"    (request the remote PACS to send the
                             study/series data)
            * "status"      (ask the internal database on the status
                             of retrieved study/series data).
            * "push"        (push study/series data to some destination
                             node)

        [--withFeedBack]
        If specified, provide console level feedback on the next operation as
        it happens. Note, if part of a chained/piped workflow, the feedback
        CLI output could corrupt downstream apps, especially if these
        apps want to consume JSON from stdin. So only use --withFeedBack in
        cases where additional downstream JSON assuming pipe operations are not
        pending.

        [--move]
        If set and called with [--retrieve], perform a DICOM movescu on the
        set of filtered SeriesInstanceUIDs using the pypx/move module.

        [--json]
        If specified, print the JSON structure related to the find event. If
        piping results to a report module, you MUST specify this.

        [--waitForUserTerminate]
        If specified, wait at program conclusion for explicit user termination.
        This is useful in dockerized runs since PACS data might still be
        in flight when the program ends, and terminating the program then
        will result in non reception of outstanding data.

        Note, if running in a container, e.g. via PACS_QR.sh, be sure to also
        specify a '-D' for debugging in conjunction with this flag. The '-D'
        runs the container in interactive tty mode, allowing for user tty
        input to be correctly interpreted.

        [-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.
    ''' + Colors.LIGHT_PURPLE + '''

    EXAMPLES

        px-find                                                         \\
            --aec CHRIS                                                 \\
            --aet FNNDSC-CHRISDEV                                       \\
            --serverIP 134.174.12.21                                    \\
            --serverPort 104                                            \\
            --PatientID 4777764                                         \\
            --json                                                      \\
            --colorize dark                                            |\\
        px-report                                                       \\
            --reportTags                                                \\
            '{
                "header":
                {
                    "study" : [
                            "PatientName",
                            "ScanDate",
                            "AccessionNumber",
                            "PatientID",
                            "PatientSex",
                            "PatientAge",
                            "PerformedStationAETitle",
                            "StudyDescription"
                            ]
                },
                "body":
                {
                    "series" : [
                            "SeriesDescription"
                            ]
                }
            }'
            --printReport tabular --colorize dark

        # Or, more succinctly
        px-find                                                         \\
            --aec CHIPS                                                 \\
            --aec ORTHANC                                               \\
            --serverIP localhost                                        \\
            --serverPort 4242                                           \\
            --json                                                      \\
            --PatientID LILLA-9731                                     |\\
        px-report                                                       \\
            --colorize dark					                            \\
            --printReport tabular                                       \\
            --reportHeaderStudyTags 'PatientName,PatientID'

    DOCKERIZED EXAMPLES

        docker run  --rm -ti                                            \\
            -p 10402:10402                                              \\
            -v /tmp:/dicom                                              \\
            fnndsc/pypx                                                 \\
            --px-find                                                   \\
            --aet CHIPS                                                 \\
            --aec ORTHANC                                               \\
            --serverIP  10.72.76.155                                    \\
            --serverPort 4242                                           \\
            --PatientID LILLA-9731                                      \\
            --colorize dark                                             \\
            --reportTags                                                \\
            '{
                "header":
                {
                    "study" : [
                            "PatientName",
                            "ScanDate",
                            "AccessionNumber",
                            "PatientID",
                            "PatientSex",
                            "PatientAge",
                            "PerformedStationAETitle",
                            "StudyDescription"
                            ]
                },
                "body":
                {
                    "series" : [
                            "SeriesDescription"
                            ]
                }
            }'

        docker run  --rm -ti                                            \\
            -p 10402:10402                                              \\
            -v /tmp:/dicom                                              \\
            fnndsc/pypx                                                 \\
            --px-find                                                   \\
            --aec CHRIS                                                 \\
            --aet FNNDSC-CHRISDEV                                       \\
            --serverIP 134.174.12.21                                    \\
            --serverPort 104                                            \\
            --PatientID 4777764                                         \\
            --colorize dark                                             \\
            --reportTags                                                \\
            '{
                "header":
                {
                    "study" : [
                            "PatientName",
                            "StudyDate",
                            "AccessionNumber",
                            "PatientID",
                            "PatientSex",
                            "PatientAge",
                            "PerformedStationAETitle",
                            "StudyDescription"
                            ]
                },
                "body":
                {
                    "series" : [
                            "SeriesDescription"
                            ]
                }
            }'

    ''' + Colors.NO_COLOUR

    if ab_shortOnly:
        return shortSynopsis
    else:
        return shortSynopsis + description


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

# db access settings
parser.add_argument(
    '--db',
    action  = 'store',
    dest    = 'dblogbasepath',
    type    = str,
    default = '/tmp/log',
    help    = 'path to base dir of receipt database')

# PACS access settings
parser.add_argument(
    '--aet',
    action  = 'store',
    dest    = 'aet',
    type    = str,
    default = 'CHRIS-ULTRON-AET',
    help    = 'aet')
parser.add_argument(
    '--aec',
    action  = 'store',
    dest    = 'aec',
    type    = str,
    default = 'CHRIS-ULTRON-AEC',
    help    = 'aec')
parser.add_argument(
    '--serverIP',
    action  = 'store',
    dest    = 'serverIP',
    type    = str,
    default = '192.168.1.110',
    help    = 'PACS server IP')
parser.add_argument(
    '--serverPort',
    action  = 'store',
    dest    = 'serverPort',
    type    = str,
    default = '4242',
    help    = 'PACS server port')
parser.add_argument(
    '--findscu',
    action  = 'store',
    dest    = 'findscu',
    type    = str,
    default = '/usr/bin/findscu',
    help    = '"findscu" executable absolute location')
parser.add_argument(
    '--movescu',
    action  = 'store',
    dest    = 'movescu',
    type    = str,
    default = '/usr/bin/movescu',
    help    = '"movescu" executable absolute location')

# Query settings
parser.add_argument(
    '--AccessionNumber',
    action  = 'store',
    dest    = 'AccessionNumber',
    type    = str,
    default = '',
    help    = 'Accession Number')
parser.add_argument(
    '--PatientID',
    action  = 'store',
    dest    = 'PatientID',
    type    = str,
    default = '',
    help    = 'Patient ID')
parser.add_argument(
    '--PatientName',
    action  = 'store',
    dest    = 'PatientName',
    type    = str,
    default = '',
    help    = 'Patient name')
parser.add_argument(
    '--PatientSex',
    action  = 'store',
    dest    = 'PatientSex',
    type    = str,
    default = '',
    help    ='Patient sex')
parser.add_argument(
    '--StudyDate',
    action  = 'store',
    dest    = 'StudyDate',
    type    = str,
    default = '',
    help    = 'Study date (YYYY/MM/DD)')
parser.add_argument(
    '--ModalitiesInStudy',
    action  = 'store',
    dest    = 'ModalitiesInStudy',
    type    = str,
    default = '',
    help    = 'Modalities in study')
parser.add_argument(
    '--Modality',
    action  = 'store',
    dest    = 'Modality',
    type    = str,
    default = '',
    help    = 'Study Modality')
parser.add_argument(
    '--PerformedStationAETitle',
    action  = 'store',
    dest    = 'PerformedStationAETitle',
    type    = str,
    default = '',
    help    = 'Performed station aet')
parser.add_argument(
    '--StudyDescription',
    action  = 'store',
    dest    = 'StudyDescription',
    type    = str,
    default = '',
    help    = 'Study description')
parser.add_argument(
    '--SeriesDescription',
    action  = 'store',
    dest    = 'SeriesDescription',
    type    = str,
    default = '',
    help    = 'Series Description')
parser.add_argument(
    '--SeriesInstanceUID',
    action  = 'store',
    dest    = 'SeriesInstanceUID',
    type    = str,
    default = '',
    help    = 'Series Instance UID')
parser.add_argument(
    '--StudyInstanceUID',
    action  = 'store',
    dest    = 'StudyInstanceUID',
    type    = str,
    default = '',
    help    = 'Study Instance UID')
parser.add_argument(
    '--ProtocolName',
    action  = 'store',
    dest    = 'ProtocolName',
    type    = str,
    default = '',
    help    = 'Protocol Name')
parser.add_argument(
    '--AcquisitionProtocolName',
    action  = 'store',
    dest    = 'AcquisitionProtocolName',
    type    = str,
    default = '',
    help    = 'Acquisition Protocol Description Name')

parser.add_argument(
    '--AcquisitionProtocolDescription',
    action  = 'store',
    dest    = 'AcquisitionProtocolDescription',
    type    = str,
    default = '',
    help    = 'Acquisition Protocol Description')

# Retrieve settings
parser.add_argument(
    '--withFeedBack',
    action  = 'store_true',
    dest    = 'withFeedBack',
    default = False,
    help    = 'If specified, print the "then" events as they happen')
parser.add_argument(
    '--then',
    action  = 'store',
    dest    = 'then',
    default = "",
    help    = 'If specified, perform another set operations "next" after the find')
parser.add_argument(
    '--move',
    action  = 'store_true',
    dest    = 'move',
    default = False,
    help    = 'If specified with --retrieve, call initiate a PACS pull on the set of SeriesUIDs using pypx/move')

parser.add_argument(
    '--json',
    action  = 'store_true',
    dest    = 'json',
    default = False,
    help    = 'If specified, dump the JSON structure relating to the query')

parser.add_argument(
    "-v", "--verbosity",
    help    = "verbosity level for app",
    dest    = 'verbosity',
    type    = int,
    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
)
parser.add_argument(
    '--version',
    help    = 'if specified, print version number',
    dest    = 'b_version',
    action  = 'store_true',
    default = False
)
parser.add_argument(
    '--waitForUserTerminate',
    help    = 'if specified, wait for user termination',
    dest    = 'b_waitForUserTerminate',
    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)

# Return the JSON result as a serialized string:
output = pypx.find(vars(args))

if args.verbosity:
    if args.json:
        try:
            print(json.dumps(output, indent = 4))
        except Exception as e:
            print(json.dumps({
                'status'    : False,
                'error'     : '%s' % e
            }))

if args.b_waitForUserTerminate:
    l_infoWindow =[
        [Colors.CYAN                                            +
        "End of program reached."                               +
        Colors.NO_COLOUR],
        [""],
        [Colors.PURPLE                                          +
        "If a PACS move/pull/retrieve was requested, not all"   +
        Colors.NO_COLOUR],
        [Colors.PURPLE                                          +
        "image data might have been received since the PACS"    +
        Colors.NO_COLOUR],
        [Colors.PURPLE                                          +
        "operates in an asynchronous manner."                   +
        Colors.NO_COLOUR],
        [""],
        [Colors.PURPLE                                          +
        "If you are running this process containerized, on"     +
        Colors.NO_COLOUR],
        [Colors.PURPLE                                          +
        "exit the container will close and no additional data"  +
        Colors.NO_COLOUR],
        [Colors.PURPLE                                          +
        "will be received."                                     +
        Colors.NO_COLOUR],
        [""],
        [Colors.BLINK_RED           +
        "ONLY EXIT IF YOU ARE SURE YOU HAVE RECEIVED ALL IMAGES!" +
        Colors.NO_COLOUR],
    ]
    tb_infoWindow = SingleTable(l_infoWindow)
    tb_infoWindow.inner_heading_row_border  = False
    print(tb_infoWindow.table)
    input("\nHit ENTER now to exit.")

sys.exit(0)
