#!/home/conda/feedstock_root/build_artifacts/dqsegdb_1771236899807/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_pla/bin/python
# Copyright (C) 2014-2020 Syracuse University, European Gravitational Observatory, and Christopher Newport University.
# Written by Ryan Fisher and Gary Hemming. See the NOTICE file distributed with this work for additional information
# regarding copyright ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# Adapted from ligolw_dq_query: Copyright (C) 2009  Larne Pekowsky, Ping Wei


#
# =============================================================================
#
#                                   Preamble
#
# =============================================================================
#


"""
Provides tools to query the DQSEGDB segment database regarding the
set of flags defined/active at or near a given time:

The only run option (no longer an option actually) prints an extensive query of the database for (a) given GPS time(s).
For the flags which were defined, it reports all active segments within the time span provided as input.
  * What is the status of all DQ flags at this time? ligolw_dq_query_dqsegdb -t <server> <gps_time>
  * Same, in a custom window (default is +/-10 seconds) ligolw_dq_query_dqsegdb --start-pad --end-pad
"""


import json
import os
import pwd
import sys

import igwn_segments as segments

from dqsegdb import apicalls
from dqsegdb._version import get_versions

from optparse import OptionParser
from urllib.parse import urlparse

PROGRAM_NAME = sys.argv[0].replace('./', '')
PROGRAM_PID = os.getpid()

try:
    USER_NAME = os.getlogin()
except:
    USER_NAME = pwd.getpwuid(os.getuid())[0]

__author__ = "Ryan Fisher <ryan.fisher@ligo.org>"
__version__ = get_versions()['version']
__id__ = get_versions()['full-revisionid']
__date__ = get_versions()['date']
__verbose_msg__ = "version: " + get_versions()['version'] + "\nfull-revisionid: " + get_versions()['full-revisionid'] \
    + "\ndirty: " + str(get_versions()['dirty']) + "\nerror: " + str(get_versions()['error']) + "\ndate: " \
    + get_versions()['date']


#
# =============================================================================
#
#                                 Command Line
#
# =============================================================================
#


def parse_command_line():
    """
    Parse the command line, return an options object
    """

    parser = OptionParser(
        version="Name: %prog\n" + __verbose_msg__,
        #version = "Name: %%prog\nVersion: %s" % get_versions()['version'],
        usage="%prog [ --version | --segment-url ] options gps-time1",
        description="Provides a report of known and active flags from the segment database for a given GPS time, "
                    "with a configurable window."
    )

    # Only one major mode, so this option is removed!
    # parser.add_option("-q", "--report",   action = "store_true", help = "Prints which flags are defined and/or "
    #                   "active at the given time +/- 10 seconds by default.")

    # Time options
    parser.add_option("-s", "--start-pad", metavar="start_pad", help="Seconds before given time(s) to include in query")
    parser.add_option("-e", "--end-pad", metavar="end_pad", help="Seconds after given time(s) to include in query")

    # Data location options
    parser.add_option("-t", "--segment-url", metavar="segment_url", help="Segment URL, e.g., https://segments.ligo.org")

    parser.add_option("-o", "--output-file", metavar="output_file",
                      help="File to which output should be written.  Defaults to stdout.")
    parser.add_option("-a", "--active-only", action="store_true",
                      help="Only report about active segments in specified window; "
                           "excludes report of known times for flags.")
    parser.add_option("-f", "--tom-formatting", action="store_true",
                      help="Tom Dent requested formatting option: condensed output into single lines.")
    parser.add_option("-b", "--separate-good-from-bad", action="store_true",
                      help="Separates flags marked by the database as being active when the ifo is in a bad state "
                           "from those that are active when the ifo is in a good state.")
    parser.add_option("--seperate-good-from-bad", action="store_true",
                      help="Separates flags marked by the database as being active when the ifo is in a bad state "
                           "from those that are active when the ifo is in a good state.  (This flag is misspelled but "
                           "retained for backwards-compatibility.  For that reason, it has precedence over "
                           "--separate-good-from-bad.)")

    options, time = parser.parse_args()

    # options had/have a misspelling: "--seperate-good-from-bad"; should be "separate"
    # to maintain backwards-compatibility, I fixed the main option with a short flag,
    # but kept the misspelled one as only a long flag; this code transfers the misspelled one to the correct one
    if options.seperate_good_from_bad is True:
        options.separate_good_from_bad = True

    # Make sure we have required arguments
    if options.segment_url:
        database_location = options.segment_url
    else:
        raise ValueError("--segment-url must be specified, ex. https://segments.ligo.org")

    return options, database_location, time


#
# =============================================================================
#
#                                 General utilities
#
# =============================================================================
#

def get_full_name(ifo, name, version):
    return '%s:%s:%d' % (ifo.strip(), name.strip(), version)


#
## =============================================================================
##
##                      S6 Outdated Methods that implement major modes
##
## =============================================================================
##
#
#def run_report(doc, process_id, engine, include_segments, times, in_segments_only):
#    all_keys       = {}
#    max_start      = {}
#    max_end        = {}
#    min_start      = {}
#    min_end        = {}
#    segment_clause = ''
#
#    if include_segments:
#        segment_clause = ' AND ' + build_segment_clause(include_segments)
#
#    # I'm not happy with the fact that this is done with four separate
#    # queries.  However it is possible that, say, there won't be a segment
#    # after the given time in which case
#    #
#    #   select MAX(end_time), MIN(start_time)
#    #   where end_time < given time and start_time > given time
#    #
#    # would miss that segment definer entirely.  Maybe there's some
#    # clever trick using outer joins to get around this, but I
#    # can't seem to come up with one at the moment.
#    #
#    # For XML-file queries this isn't a problem, since local queries
#    # are fast.  But talking over the LDBD server takes a noticable amount
#    # of time per-query
#    #
#    for tm in map(lambda x: int(x[0]), times):
#        # Segments we are in the midsts of
#        rows = engine.query("""SELECT segment_definer.ifos, segment_definer.name, segment_definer.version,
#             segment.start_time, segment.end_time
#             FROM segment_definer, segment
#             WHERE segment_definer.segment_def_id = segment.segment_def_id
#             AND %d BETWEEN segment.start_time AND segment.end_time
#             %s """ % (tm, segment_clause))
#
#        in_times = {}
#        for ifo, name, version, start_time, end_time in rows:
#            full_name = get_full_name(ifo, name, version)
#            in_times[full_name] = [start_time, end_time]
#            print('%-45s [%d %d %d)' % (full_name, start_time, tm, end_time))
#
#        if not in_segments_only:
#            # Segments we are between. Latest end time before of interest.
#            out_times = {}
#
#            rows = engine.query("""SELECT segment_definer.ifos, segment_definer.name, segment_definer.version,
#                                MAX(segment.end_time)
#                FROM segment_definer, segment
#                WHERE segment_definer.segment_def_id = segment.segment_def_id
#                AND segment.end_time < %d %s
#                GROUP BY segment_definer.ifos, segment_definer.name, segment_definer.version""" % \
#                    (tm, segment_clause))
#
#
#            for ifo, name, version, end_time in rows:
#                full_name = get_full_name(ifo, name, version)
#                out_times[full_name] = ['%d)' % end_time, '[now']
#
#            # The next start time after the time of interest
#            rows = engine.query("""SELECT segment_definer.ifos, segment_definer.name, segment_definer.version,
#                                MIN(segment.start_time)
#                FROM segment_definer, segment
#                WHERE segment_definer.segment_def_id = segment.segment_def_id
#                AND segment.start_time > %d %s
#                GROUP BY segment_definer.ifos, segment_definer.name, segment_definer.version""" % (tm, segment_clause))
#
#            for ifo, name, version, start_time in rows:
#                full_name = get_full_name(ifo, name, version)
#                value = full_name in out_times and [out_times[full_name][0], '[%s' % start_time] or ['never)', '[%s' % start_time]   # noqa: E501
#                out_times[full_name] = value
#
#            for key in out_times:
#                if key not in in_times:
#                    value = out_times[key]
#                    print('%-45s %s %d %s' % (key, value[0], tm, value[1]))
#
#
#def run_active(doc, process_id, engine, include_segments, times):
#    time_clause    = build_time_clause('segment', times)
#    segment_clause = ''
#
#    if include_segments:
#        segment_clause = ' AND ' + build_segment_clause(include_segments)
#
#    rows = engine.query("""SELECT segment_definer.ifos, segment_definer.name, segment_definer.version,
#                        segment_definer.comment, segment.start_time, segment.end_time
#        FROM segment_definer, segment_summary, segment
#        WHERE segment_definer.segment_def_id = segment_summary.segment_def_id
#        AND   segment.start_time BETWEEN segment_summary.start_time AND segment_summary.end_time
#        AND   segment_definer.segment_def_id = segment.segment_def_id
#        AND """ + time_clause + segment_clause)
#
#
#    distinct_names = []
#    distinct_rows = []
#    for x in rows:
#      if x[0:3] not in distinct_names:
#         distinct_names.append(x[0:2])
#         distinct_rows.append(x)
#
#
#    seg_def_table = lsctables.New(lsctables.SegmentDefTable, columns = ["process_id", "segment_def_id", "ifos",
#                                                                        "name", "version", "comment"])
#    doc.childNodes[0].appendChild(seg_def_table)
#
#    for ifos, name, version, comment, start_time, end_time in distinct_rows:
#        seg_def_id                     = seg_def_table.get_next_id()
#        segment_definer                = lsctables.SegmentDef()
#        segment_definer.process_id     = process_id
#        segment_definer.segment_def_id = seg_def_id
#        segment_definer.ifos           = ifos.strip()
#        segment_definer.name           = name
#        segment_definer.version        = version
#        segment_definer.comment        = comment
#
#        seg_def_table.append(segment_definer)
#
#
#
#
#def run_defined(doc, process_id, engine, include_segments, times):
#    time_clause    = build_time_clause('segment_summary', times)
#    segment_clause = ''
#
#    if include_segments:
#        segment_clause = ' AND ' + build_segment_clause(include_segments)
#
#    rows = engine.query("""SELECT segment_definer.ifos, segment_definer.name, segment_definer.version,
#                        segment_definer.comment, segment_summary.start_time, segment_summary.end_time
#        FROM segment_definer, segment_summary
#        WHERE segment_definer.segment_def_id = segment_summary.segment_def_id
#        AND """ + time_clause + segment_clause)
#
#    seg_def_table = lsctables.New(lsctables.SegmentDefTable, columns = ["process_id", "segment_def_id", "ifos",
#                                                                        "name", "version", "comment"])
#    doc.childNodes[0].appendChild(seg_def_table)
#
#    for ifos, name, version, comment, start_time, end_time in rows:
#        seg_def_id                     = seg_def_table.get_next_id()
#        segment_definer                = lsctables.SegmentDef()
#        segment_definer.process_id     = process_id
#        segment_definer.segment_def_id = seg_def_id
#        segment_definer.ifos           = ifos.strip()
#        segment_definer.name           = name
#        segment_definer.version        = version
#        segment_definer.comment        = comment
#
#        seg_def_table.append(segment_definer)
#

#
# =============================================================================
#
#                                     Main
#
# =============================================================================
#


if __name__ == '__main__':
    options, database_location, time = parse_command_line()

    o = urlparse(options.segment_url)
    protocol = o.scheme
    server = o.netloc

    start_pad = 0
    end_pad = 0

    if len(time) > 1:
        raise ValueError("Please provide one central GPS time for your query.")
    else:
        time = int(time[0])

    if options.start_pad:
        start_pad = abs(int(options.start_pad))
        if start_pad > 1000:
            raise ValueError("start_pad greater than 1000 seconds; Please contact the segment database maintainers "
                             "if you believe you need to issue this large a query.")
    else:
        start_pad = 10

    if options.end_pad:
        end_pad = abs(int(options.end_pad))
        if end_pad > 1000:
            raise ValueError("end_pad greater than 1000 seconds; Please contact the segment database maintainers "
                             "if you believe you need to issue this large a query.")
    else:
        end_pad = 10

    time_seg = segments.segment(int(time) - start_pad, int(time) + end_pad)
    print("Window Queried:")
    print(time_seg)
    print("===============")

    # First print active segments in window:
    result, query_url = apicalls.reportActive(protocol, server, True, False, time_seg[0], time_seg[1])
    dict = json.loads(result)
    active_results = {}
    active_results_not_bad = {}
    #manual_debug = True
    manual_debug = False
    if manual_debug:
        import pdb
        pdb.set_trace()
    for i in dict['results']:
        ifo = i['ifo']
        name = i['name']
        version = str(i['version'])
        flag_string = ":".join([ifo, name, version])
        for active_segment in i['active']:
            start_time = active_segment[0]
            end_time = active_segment[1]
            if flag_string not in active_results:
                active_results[flag_string] = segments.segmentlist([segments.segment(start_time, end_time)])
                if i['metadata']['active_indicates_ifo_badness'] is not True:
                    active_results_not_bad[flag_string] = segments.segmentlist([segments.segment(start_time, end_time)])   # noqa: E501
            else:
                active_results[flag_string].append(segments.segment(start_time, end_time))
                if i['metadata']['active_indicates_ifo_badness'] is not True:
                    active_results_not_bad[flag_string].append(segments.segmentlist([segments.segment(start_time, end_time)]))   # noqa: E501

    if not options.tom_formatting:
        tom = False
    else:
        tom = True
    if not options.separate_good_from_bad:
        print("Flags active in provided window:")
        for i in active_results:
            if tom is True:
                text = "Flag name: "
                text += i
                text += " ; Active: "
                text += str(active_results[i])
                print(text)
            else:
                print("Flag name:")
                print(i)
                print("Active segments:")
                print(active_results[i])
    else:
        active_results_bad = {}
        print("Flags active in provided window that indicate bad ifo state:")
        for i in active_results:
            if i not in active_results_not_bad:
                if tom is True:
                    text = "Flag name: "
                    text += i
                    text += " ; Active: "
                    text += str(active_results[i])
                    print(text)
                else:
                    print("Flag name:")
                    print(i)
                    print("Active segments:")
                    print(active_results[i])
        print("Flags active in provided window that indicate good ifo state:")
        for i in active_results:
            if i in active_results_not_bad:
                if tom is True:
                    text = "Flag name: "
                    text += i
                    text += " ; Active: "
                    text += str(active_results[i])
                    print(text)
                else:
                    print("Flag name:")
                    print(i)
                    print("Active segments:")
                    print(active_results[i])

    # Now print defined but inactive flag types:
    if not options.active_only:
        known_result, query_url = apicalls.reportKnown(protocol, server, True, False, time_seg[0], time_seg[1])
        known_dict = json.loads(known_result)
        print("Flags defined, but contain no active segments in requested window:")
        known_results = {}
        for i in known_dict['results']:
            ifo = i['ifo']
            name = i['name']
            version = str(i['version'])
            flag_string = ":".join([ifo, name, version])
            if flag_string not in active_results:
                if tom is True:
                    text = "Flag name: "
                    text += flag_string
                    text += " ; Known Times overlapping query: "
                    for j in i['known']:
                        text += " " + str(j) + " "
                        start_time = j[0]
                        end_time = j[1]
                        if flag_string not in known_results:
                            known_results[flag_string] = segments.segmentlist([segments.segment(start_time, end_time)])
                        else:
                            known_results[flag_string].append(segments.segment(start_time, end_time))
                    print(text)
                else:
                    print("Flag name:")
                    print(flag_string)
                    print("Known times overlapping window:")
                    for j in i['known']:
                        print(j)
                        start_time = j[0]
                        end_time = j[1]
                        if flag_string not in known_results:
                            known_results[flag_string] = segments.segmentlist([segments.segment(start_time, end_time)])
                        else:
                            known_results[flag_string].append(segments.segment(start_time, end_time))
        if options.separate_good_from_bad:
            print("Flags marked as active means ifo operating well, that are defined but inactive in this period."
                  "  These should be considered as possible vetoes!")
            for i in known_dict['results']:
                ifo = i['ifo']
                name = i['name']
                version = str(i['version'])
                flag_string = ":".join([ifo, name, version])
                if flag_string not in active_results:
                    if i['metadata']['active_indicates_ifo_badness'] is not True:
                        if tom is True:
                            text = "Flag name: "
                            text += flag_string
                            text += " ; Known Times overlapping query with no active times: "
                            for j in i['known']:
                                text += " " + str(j) + " "
                            print(text)
                        else:
                            print("Flag name:")
                            print(flag_string)
                            print("Known times overlapping window with no active times:")
                            for j in i['known']:
                                print(j)

    if options.output_file:
        # Build json output file:
        out_dict = {}
        out_dict['Active Results'] = active_results
        if not options.active_only:
            out_dict['Known but Inactive Results'] = known_results
        json_string_out = json.dumps(out_dict)
        fh = open(options.output_file, 'w')
        fh.write(json_string_out)
        fh.close()
