#!/opt/anaconda/bin/python

import atexit
import sys
import json
import socket
from datetime import datetime
import time
import owslib
from owslib.wps import WebProcessingService
from owslib.wps import monitorExecution
import requests
import lxml.etree as etree
import string
import pytz
#from requests.auth import HTTPBasicAuth

sys.path.append('/opt/anaconda/bin/')
import cioppy
ciop = cioppy.Cioppy()

# define the exit codes
SUCCESS = 0
ERR_RUNTIME = 10
DATA_PIPELINE = ''
API_KEY = ''
USERNAME = ''

class WPSException(Exception):
    pass

class RecastException(Exception):
    pass

# add a trap to exit gracefully
def clean_exit(exit_code):

    log_level = 'INFO'
    if exit_code != SUCCESS:
        log_level = 'ERROR'

    msg = {SUCCESS: 'Processing successfully concluded',
           ERR_RUNTIME: 'Failed to run process'
          }

    ciop.log(log_level, msg[exit_code])

def get_inputs(reference, metadata):

    inputs = []

    lon = ciop.getparam('longitude')
    lat = ciop.getparam('latitude')
    extent = ciop.getparam('extent')

    inputs.append(('source', reference))
    inputs.append(('longitude', lon))
    inputs.append(('latitude', lat))
    inputs.append(('extent', extent))
    inputs.append(('quotation', ciop.getparam('quotation')))
    inputs.append(('_T2Username', USERNAME))

    return inputs

def get_metadata(reference):

    # retrieve information fro the catalogue for metrics and systematic processing tracking
    search = ciop.search(end_point=reference,
                         params=[],
                         output_fields='self,title,enclosure,identifier,startdate,'
                         + 'enddate,published,updated,related,wkt',
                         model='GeoTime')

    return search[0]

def metrics(reference, metadata, event):

    size = ''
    project = ''
    partner = ''
    lifecycle = ''
    status = ''

    payload = {
        'workflow': {
            'name': DATA_PIPELINE,
            'id': '%s'},
        'hostname': socket.gethostname(),
        'start' : metadata['startdate'],
        'end' : metadata['enddate'],
        'identifier' : metadata['identifier'],
        'role' : 'dataset',
        'event' : event,
        'geometry' : metadata['wkt'],
        'size' : size,
        'project' : project,
        'partner' : partner,
        'lifecycle' : lifecycle,
        'status' : status
        }

    endpoint = 'http://metrics.terradue.com/event-cache/metrics/%s_%s' % (metadata['identifier'],
                                                                          event)

    headers = {"Content-Type": "application/json"}

    #request_metrics = requests.put(endpoint, 
    #                               data=json.dumps(payload), 
    #                               headers=headers)

def track(reference, metadata, stream, status_location=''):

    payload = {
        "features": [
            {
                "id": metadata['identifier'],
               
                "properties": {
                    "links": [ 
                        {
                            "@href": metadata['self'],
                            "@rel": "via",
                            "@title": "Resource source of the processing",
                            "@type": "application/json"
                        },
                        {
                            "@href": "http://www.terradue.com/api/data-pipeline",
                            "@rel": "profile",
                            "@title": "This file is a compliant Terradue data pipeline",
                            "@type": "application/json"
                        }
                    ],
                    "categories": [
                        {
                            "@term": stream,
                            "@label": "Data pipeline " + stream
                        }
                    ],
                    "date": metadata['startdate'] + '/' + metadata['enddate'],
                    "identifier": metadata['identifier'],
                    "spatial": metadata['wkt'],
                    "statusLocation" : status_location
                },
                "type": "Feature"
            }
        ],
        "type": "FeatureCollection"
    }

    endpoint = 'https://catalog.terradue.com/%s' % DATA_PIPELINE

    headers = {"Content-Type": "application/json"}

    request_track = requests.post(endpoint,
                                  data=json.dumps(payload),
                                  headers=headers,
                                  auth=(USERNAME, API_KEY))

    ciop.log('INFO', 'Track data-stream %s (%s)' % (stream, str(request_track.status_code)))

def track_atom(reference, metadata, stream, inputs, execution_request):

    inputs = dict(inputs)

    template = '/application/data-driven/etc/empty_template.xml'
    
    tree = etree.parse(template)
    root = tree.getroot()

    # get the elements using xpath
    el_title = root.xpath('/a:feed/a:entry/a:title', 
                      namespaces={'a':'http://www.w3.org/2005/Atom'})
    el_via_link = root.xpath('/a:feed/a:entry/a:link', 
                         namespaces={'a':'http://www.w3.org/2005/Atom'})
    el_operations = root.xpath('/a:feed/a:entry/b:offering/b:operation', 
                           namespaces={'a':'http://www.w3.org/2005/Atom',
                                       'b':'http://www.opengis.net/owc/1.0'})
    el_execute_post = root.xpath('/a:feed/a:entry/b:offering/b:operation[@code="Execute"]/b:request', 
                             namespaces={'a':'http://www.w3.org/2005/Atom',
                                         'b':'http://www.opengis.net/owc/1.0'})
    el_spatial = root.xpath('/a:feed/a:entry/c:spatial', 
                      namespaces={'a':'http://www.w3.org/2005/Atom',
                       'c':'http://purl.org/dc/terms/'})
    el_date = root.xpath('/a:feed/a:entry/d:date', 
                      namespaces={'a':'http://www.w3.org/2005/Atom',
                       'd':'http://purl.org/dc/elements/1.1/'})
    el_published = root.xpath('/a:feed/a:entry/a:published', 
                      namespaces={'a':'http://www.w3.org/2005/Atom'})
    el_identifier = root.xpath('/a:feed/a:entry/d:identifier', 
                      namespaces={'a':'http://www.w3.org/2005/Atom',
                       'd':'http://purl.org/dc/elements/1.1/'})
    el_category = root.xpath('/a:feed/a:entry/a:category', 
                      namespaces={'a':'http://www.w3.org/2005/Atom'})

    # update the values 
    el_title[0].text = inputs['title']
    el_via_link[1].attrib['href'] = inputs['via_link_href'] 
    el_operations[0].attrib['href'] = inputs['describe_op_href']
    el_operations[1].attrib['href'] = inputs['capabilities_op_href']

    if 'status_op_href' in inputs.keys():
        el_operations[2].attrib['href'] = inputs['status_op_href']
    else:
        el_operations[2].attrib['href'] = ''

    el_operations[3].attrib['href'] = inputs['execute_op_href']

    if execution_request is not None:
        el_execute_post[0].addnext(execution_request)
    
    # add intermediate results catalogue OSD
    if 'intermediate_results_osd' in inputs.keys():
        
        xml_string = """<owc:offering xmlns:owc="http://www.opengis.net/owc/1.0" code="http://www.opengis.net/spec/owc-atom/1.0/opensearch">
                <owc:operation code="search" method="GET" type="application/opensearchdescription+xml" 
href="%s"/>
</owc:offering>""" % inputs['intermediate_results_osd']
        
        el_cat_offering = root.xpath('/a:feed/a:entry/b:offering', 
                                     namespaces={'a':'http://www.w3.org/2005/Atom',
                                         'b':'http://www.opengis.net/owc/1.0'})
        
        el_cat_offering[0].addnext(etree.fromstring(xml_string))
        
    # add catalogue offering with OSD returned 
    if 'final_results_osd' in inputs.keys():
        
        xml_string = """<owc:offering xmlns:owc="http://www.opengis.net/owc/1.0" code="http://www.opengis.net/spec/owc-atom/1.0/opensearch">
                <owc:operation code="search" method="GET" type="application/opensearchdescription+xml" 
href="%s"/>
</owc:offering>""" % inputs['final_results_osd']
        
        el_cat_offering = root.xpath('/a:feed/a:entry/b:offering', 
                                     namespaces={'a':'http://www.w3.org/2005/Atom',
                                         'b':'http://www.opengis.net/owc/1.0'})
        
        el_cat_offering[0].addnext(etree.fromstring(xml_string))
        
    el_spatial[0].text = inputs['spatial']
    el_date[0].text = inputs['date']
    el_published[0].text = inputs['published']
    el_identifier[0].text = inputs['identifier']
    el_category[0].attrib['term']  = stream
    el_category[0].attrib['label']  = 'Status is %s' % stream
    
    endpoint = 'https://catalog.terradue.com/%s' % DATA_PIPELINE

    headers = {"Content-Type": "application/atom+xml", "Accept": "application/xml"}

    request_track = requests.post(endpoint,
                                  data=etree.tostring(tree, pretty_print=True),
                                  headers=headers,
                                  auth=(USERNAME, API_KEY))

    ciop.log('INFO', 'Track data-stream %s (%s)' % (stream, str(request_track.status_code)))

def recast(reference, metadata, execution):

    recast_process_id = 'dataPublication'
    wps_url = 'https://recast.terradue.com/t2api/ows'

    osd = execution.processOutputs[0].reference
    ciop.log('INFO', 'Recast osd: %s' % osd)

    wps = WebProcessingService(wps_url, verbose=False, skip_caps=True)

    recast_inputs = [('items', osd),
                     ('index', DATA_PIPELINE),
                     ('_T2ApiKey', API_KEY),
                     ('_T2Username', USERNAME)]

    ciop.log('INFO', 'Recast WPS process submission')
    recast_execution = wps.execute(recast_process_id, recast_inputs, output = [('result_osd', True)])

    ciop.log('INFO', 'Recast WPS process monitoring (%s)' % recast_execution.statusLocation)

    tries = 10
    for i in range(tries):
        try:
            monitorExecution(recast_execution, sleepSecs=30)
        except requests.exceptions.ReadTimeout:
            if i < tries - 1:
                ciop.log('INFO', 'Recast WPS process monitoring #%s' % str(i+1))
                time.sleep(5)
                continue
            else:
                raise
        break

    if recast_execution.isSucceded():
        tracking_inputs.append(('final_results_osd', etree.fromstring(recast_execution.processOutputs[0].data[0]).xpath('./@href')[0]))    
        
    return recast_execution.isSucceded()

def execute(reference):
   
    recovery = ciop.getparam('recovery')
    
    if recovery == 'No':
        
        ciop.log('INFO', 'Process reference: %s' % reference)
        
    else:

        search = ciop.search(end_point=reference, 
                             params=[],
                             output_fields='link:via')
    
        reference = search[0]['link:via']
        ciop.log('INFO', 'Recovery mode for %s' % reference)

    wps_url = ciop.getparam('wps_url')
    process_id = ciop.getparam('process_id')

    metadata = get_metadata(reference)

    inputs = get_inputs(reference, metadata)

    ciop.log('INFO', 'Identifier: %s (%s)' % (metadata['identifier'], reference))
      
    #track(reference, metadata, 'source-in')

    wps = WebProcessingService(wps_url, verbose=False, skip_caps=True)

    ciop.log('INFO', '%s WPS process submission' % process_id)

    published = datetime.now(pytz.timezone('utc')).replace(microsecond=0).isoformat()

    # build the content for tracking
    global tracking_inputs 
    
    tracking_inputs = [('identifier', metadata['identifier']),
                       ('title', metadata['identifier']),
                       ('via_link_href', string.replace(metadata['self'], 'rdf', 'atom')),
                       ('spatial', metadata['wkt']),
                       ('date', metadata['startdate'] + '/' + metadata['enddate']),
                       ('capabilities_op_href', '%s?service=WPS&version=1.0.0&request=getCapabilities' % wps_url),
                       ('describe_op_href', 
                        '%s?request=DescribeProcess&service=WPS&version=1.0.0&Identifier=%s' % 
                        (wps_url, process_id)),
                       ('execute_op_href', wps_url),
                       ('published', published)]
                       
    execution_request = None

    try:
        execution = wps.execute(process_id, inputs, output=[('result_osd', False)])

        execution_request = execution.buildRequest(process_id, inputs, output = [('result_osd', False)])

        # add the status location
        tracking_inputs.append(('status_op_href', execution.statusLocation))
              
        track_atom(reference, 
                   metadata,
                   'source-in', 
                   tracking_inputs, 
                   execution_request)
                
        # if it's assynchronous exit function here and the entry will be source-in
        
        # if it's synchronous, carry-on
        ciop.log('INFO', '%s WPS process monitoring (%s)' % (process_id, execution.statusLocation))
        monitorExecution(execution)

        if execution.isSucceded():
            ciop.log('INFO', '%s WPS process is succeded' % process_id)
            tracking_inputs.append(('intermediate_results_osd', execution.processOutputs[0].reference))
        else:
            raise WPSException()

        if recast(reference, metadata, execution):
            ciop.log('INFO', 'Recast WPS process is succeded')
        else:
            raise RecastException()

        metrics(reference, metadata, 'an_event')

        #track(reference, metadata, 'source-out', execution.statusLocation)
        #track_atom(reference, 
        #           metadata,
        #           'source-out', 
        #           tracking_inputs, 
        #           execution_request)
        stream = 'source-out' 
    
    except owslib.util.ServiceException:
        ciop.log('ERROR', 'The value for <identifier> seems to be wrong (%s)' % process_id)
        stream = 'source-err'
    except WPSException:
        ciop.log('ERROR', '%s WPS process failed' % process_id)
        stream = 'source-err'
    except RecastException:
        ciop.log('ERROR', 'Recast WPS process failed')
        stream = 'source-err'
    except requests.exceptions.ReadTimeout:
        ciop.log('ERROR', 'HTTPSConnectionPool timeout')
        stream = 'source-err'
    except:
        ciop.log('ERROR', 'Unexpected error')
        stream = 'source-err'
    finally:
        track_atom(reference, 
                   metadata,
                   stream, 
                   tracking_inputs, 
                   execution_request)
        
def main():

    global DATA_PIPELINE
    DATA_PIPELINE = ciop.getparam('data_pipeline')

    global USERNAME
    USERNAME = ciop.getparam('username')

    global API_KEY
    API_KEY = ciop.getparam('api_key')

    for reference in sys.stdin:
        execute(reference.rstrip())

try:
    main()
except SystemExit as e:
    if e.args[0]:
        clean_exit(e.args[0])
    raise
else:
    atexit.register(clean_exit, 0)
