#!/usr/bin/env python
"""
    Very simple client script used to get nearest squid server using the RESTful API.
"""
import urllib2
import sys
import re
import os
import syslog
from optparse import OptionParser
from subprocess import Popen, PIPE

from shoal_client import config as config

syslog.openlog("shoal-client")

server = config.shoal_server_url
default_http_proxy = config.default_squid_proxy

data = None
frontier = False
numsquids = 2
dump = False
closest_http_proxy = ''
env_proxy = ''
no_shoal_server = False


def get_args():
    """
        gets server and dump variables from command line arguments
    """
    global server
    global dump
    global frontier
    global numsquids

    p = OptionParser()
    p.add_option("-s", "--server", action="store", type="string", dest="server",
                 help="Also needs string for specifying the shoal server to use. " +
                 "Takes precedence over the option in config file.")
    p.add_option("-d", "--dump", action="store_true", dest="dump",
                 help="Print closest proxies to terminal for debugging "+
                 "instead of executing a cvfms-talk command to update the active configuration.")
    p.add_option("-f", "--frontier", action="store_true", dest="frontier",
                 help="Outputs a string suitable for use as a frontier proxy eviroment variable" +
                 "instead of executing a cvfms-talk command to update the active configuration.")
    p.add_option("-n", "--squids", action="store", type="int", dest="numsquids",
                 help="Specifies the number of squids to retrieve" +
                 "from the shoal-server (default is 2).")
    (options, args) = p.parse_args()

    if options.server:
        server = options.server
    if options.dump:
        dump = True
    if options.frontier:
        frontier = True
    if options.numsquids:
        numsquids = options.numsquids

def convertServerData(val):
    """
        converts val to digits if it's not already or else return None
    """
    if val.isdigit():
        return int(val)
    else:
        try:
            return float(val)
        except:
            if "null" in val:
                return None
            else:
                return unicode(val.strip("\""))


def parseServerData(jsonStr):
    """
        creates a multidimensional server data dictionary indexed by
        unicode integers with dataTypes, geo_data and geoDataTypes. Each
        respective entry holds the appropriate dataTypes and geoDataTypes
        found in jsonStr
    """

    # TODO should load this from a config file as it has to match the server
    # Nested properties (i.e geo_data) needs to be handled separately
    dataTypes = ["load", "distance", "squid_port", "last_active", "created", \
                 "external_ip", "hostname", "public_ip", "private_ip"]

    geoDataTypes = ["city", "region_name", "area_code", "time_zone", "dma_code", \
                    "metro_code", "country_code3", "latitude", "postal_code", \
                    "longitude", "country_code", "country_name", "continent"]

    # don't really care about data here
    # it is just a simple way to get number of nearest squids
    p = re.compile("\"" + dataTypes[0] + "\": ([^,}]+)")
    numNearestSquids = len(p.findall(jsonStr))
    ## compiles regex "load": ([^,}]+), although it doesn't really matter that fact that it's a load
    ## this will find the number of above matches in jsonStr and return into numNearestSquids
    ## therefore each match in json is a 'nearest' squid

    # initalize the dictionaries
    outDict = {}
    for i in range(0, numNearestSquids):
        outDict[unicode(str(i))] = {}
        outDict[unicode(str(i))][unicode("geo_data")] = {}
    ## creates a multidimensional dict with each key 1 being u'i' (i in unicode)
    ## and key 2 being "geo_data" for all entries

    # TODO probably don't need seperate regexes
    # test using geodata one for both
    # for each item in dataTypes, compile a regex for that item and find all
    # the matches with jsonStr and put those matches in dataList.
    for dataType in dataTypes:
        p = re.compile("\"" + dataType + "\": ([^,]+)[,|}]")
        dataList = p.findall(jsonStr)
        for i, val in enumerate(dataList):
            outDict[unicode(str(i))][unicode(dataType)] = convertServerData(val)
    ## outDict is a multidimensional dict that now holds a val in each dataType per i
    ## same as above just for geoDataTypes
    for geoDataType in geoDataTypes:
        p = re.compile("\"" + geoDataType + "\": (\"[^\"]*|[^,]*)")
        dataList = p.findall(jsonStr)
        for i, val in enumerate(dataList):
            outDict[unicode(str(i))][unicode("geo_data")][unicode(geoDataType)] = convertServerData(val)
    ## outDict in geo_data for each geoDataType holds a val
    return outDict

get_args()


    # reads data from a URL from a Shoal server, parses it, prepares a list of proxies
    # stored in cvmfs_http_proxy, executes command to set CVMFS proxies if dump is not specified.


## try to load default value from env variable
try:
    syslog.syslog(syslog.LOG_INFO, "Checking Enviroment Variable for default proxy")
    env_proxy = os.environ['HTTP_PROXY']
    syslog.syslog(
        syslog.LOG_INFO,
        "Enviroment Variable located, adding: %s as default proxy" % env_proxy)
    if env_proxy != "":
        env_proxy += ";"

except KeyError as e:
    syslog.syslog(syslog.LOG_ERR, "No HTTP_PROXY enviroment variable found")

## read server data (if it can be read) into a dictionary called data
try:
    #if there is a bad proxy set we will never reach shoal-server
    #this goes direct to avoid any bad configuration
    proxy_handler = urllib2.ProxyHandler({})
    opener = urllib2.build_opener(proxy_handler)
    f = opener.open(server, timeout=1)
    # data = json.loads(f.read())
    data = parseServerData(f.read())
    syslog.syslog(syslog.LOG_DEBUG, "Got data from %s" % server)
except (urllib2.URLError, ValueError), e:
    # This is where the client now exits if it can't reach shoal, might be worth
    # refactoring instead of injecting code here to reuse the proceeding code.
    #checkEnvVariable()
    #checkConfig()
    syslog.syslog(syslog.LOG_ERR, "Unable to reach shoal-server, reverting to defaults")
    no_shoal_server = True
    #sys.exit(1)

# If the shoal_server was reachable
if not no_shoal_server:

    ## iterate through the data dict and use all hostname and squid_port keys
    ## to create addresses for squids in closest_http_proxy
    syslog.syslog(syslog.LOG_INFO, "Received data from server, processing.")

    ## iterate through the data dict and use all hostname and squid_port keys
    ## to create addresses for squids in closest_http_proxy
    if frontier:
        for i in range(0, numsquids):
            try:
                closest_http_proxy += '(proxyurl=http://%s:%s)' % (data['%s'%i]['hostname'], data['%s'%i]['squid_port'])
            except KeyError, e:
                syslog.syslog(
                    syslog.LOG_ERR,
                    "The data returned from '%s' was missing the key: %s. "
                    "Please ensure the server is running the latest version "
                    "of Shoal-Server." % (server, e))
                print os.getenv("FRONTIER_SERVER", "")
                sys.exit(2)

    else:
        for i in range(0, numsquids):
            try:
                closest_http_proxy += 'http://%s:%s;' % (data['%s'%i]['hostname'], data['%s'%i]['squid_port'])
            except KeyError, e:
                syslog.syslog(
                    syslog.LOG_ERR,
                    "The data returned from '%s' was missing the key: %s. Please ensure the "
                    "server is running the latest version of Shoal-Server." % (server, e))
                sys.exit(2)

    #need to reformat default string for frontier nodes
    if frontier:
        new_defaults = ''
        tmpproxies = default_http_proxy.split(';')
        for proxy in tmpproxies:
            new_defaults += '(proxyurl=%s)' % proxy
        default_http_proxy = new_defaults


    cvmfs_http_proxy = "\"" + closest_http_proxy + default_http_proxy + "\""
    syslog.syslog(syslog.LOG_DEBUG, "Data parsing complete.")

    if dump:
        if not frontier:
            cvmfs_http_proxy = "\"" + closest_http_proxy + env_proxy + default_http_proxy + "\""
        syslog.syslog(syslog.LOG_INFO, "Dumping proxy string")
        print cvmfs_http_proxy

    elif frontier:
        #retrieve frontier env variable and insert new squids
        try:
            frontier_var = os.getenv("FRONTIER_SERVER", "")
        except:
            syslog.syslog(
                syslog.LOG_ERR,
                "FRONTIER_SERVER env variable unset, unable to run with --frontier option")
            sys.exit(1)
        (frontier_output, replacements) = re.subn(r'(\(.*?\))(\(proxyurl.*?\))',
                                                  r'\1' + closest_http_proxy + r'\2',
                                                  frontier_var, 1)
        # if no replacmeents, then there was no proxyurl to begin with
        if replacements == 0:
            frontier_output = frontier_output + closest_http_proxy
        print frontier_output

    else:
        cvmfs_http_proxy = closest_http_proxy + env_proxy + default_http_proxy
        syslog.syslog(syslog.LOG_DEBUG, "Data parsing complete.")
        syslog.syslog(syslog.LOG_INFO, "Setting %s as proxy" % cvmfs_http_proxy)

        syslog.syslog(syslog.LOG_DEBUG, "Executing 'cvmfs_talk proxy set %s'" % cvmfs_http_proxy)
        try:
            p = Popen(["cvmfs_talk", "proxy", "set", cvmfs_http_proxy], stdout=PIPE, stderr=PIPE)
            output, errors = p.communicate()
            if errors:
                syslog.syslog(syslog.LOG_ERR, errors)
            if output:
                syslog.syslog(syslog.LOG_DEBUG, output)
            if p.returncode:
                syslog.syslog(
                    syslog.LOG_ERR,
                    "WARNING: CVMFS proxies may not have been set correctly for all repos")
                sys.exit(p.returncode)
            else:
                syslog.syslog(syslog.LOG_INFO, "CVMFS proxies set to %s" % cvmfs_http_proxy)
        except OSError as e:
            syslog.syslog(syslog.LOG_ERR, "Could not execute cvmfs_talk: %s" % e.strerror)
            sys.exit(e.errno)

#if we get here the client as unable to connect to the shoal_server
#First check if there is a value in the env variable
#Second check if there is a valid default in shoal_client.conf
else:

    cvmfs_http_proxy = env_proxy + default_http_proxy
    syslog.syslog(syslog.LOG_INFO, "Setting %s as proxy" % cvmfs_http_proxy)
    ## if dump, don't set CVMFS proxies
    if dump:
        syslog.syslog(syslog.LOG_INFO, "Dumping proxy string")
        print cvmfs_http_proxy
    else:
        syslog.syslog(syslog.LOG_DEBUG, "Executing 'cvmfs_talk proxy set %s'" % cvmfs_http_proxy)
        try:
            p = Popen(["cvmfs_talk", "proxy", "set", cvmfs_http_proxy], stdout=PIPE, stderr=PIPE)
            output, errors = p.communicate()
            if errors:
                syslog.syslog(syslog.LOG_ERR, errors)
            if output:
                syslog.syslog(syslog.LOG_DEBUG, output)
            if p.returncode:
                syslog.syslog(
                    syslog.LOG_ERR,
                    "WARNING: CVMFS proxies may not have been set correctly for all repos")
                sys.exit(p.returncode)
            else:
                syslog.syslog(syslog.LOG_INFO, "CVMFS proxies set to %s" % cvmfs_http_proxy)
        except OSError as e:
            syslog.syslog(syslog.LOG_ERR, "Could not execute cvmfs_talk: %s" % e.strerror)
            sys.exit(e.errno)
