#!/usr/bin/env python
## Copyright (C) 2015 Ryan Fisher, Gary Hemming
# 
# 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/>.
# Copyright (C) 2015 Ryan Fisher, Gary Hemming
# Adapted from ligolw_dq_query: Copyright (C) 2009  Larne Pekowsky, Ping Wei


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


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


The --report option 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 --report
  * In a custom window (default is +/-10 seconds? ligolw_dq_query --start-pad --end-pad
"""


from optparse import OptionParser

import sys
import os
import pwd

from glue import segments

from glue import git_version

from dqsegdb import urifunctions
from dqsegdb import apicalls
import json
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 <rpfisher@phy.syr.edu>"
__version__ = "git id %s" % git_version.id
__date__ = git_version.date

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


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

    parser = OptionParser(
        version = "Name: %%prog\n%s" % git_version.verbose_msg,
        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")

    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.")
   
    options, time = parser.parse_args()

    # 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]
#                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.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={}
    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)])
            else:
                active_results[flag_string].append(segments.segment(start_time,end_time))
    
    print "Flags active in provided window:"
    for i in active_results:
        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:
                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.output_file:
        # Build json output file:
        out_dict={}
        out_dict['Active Results']=active_results
        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()
        


