#!/usr/bin/python

import ConfigParser
import getopt
import logging
import os
import pickle
import ssl
import sys
import time
import urllib2

try:
    from xml.etree import ElementTree
except ImportError:
    from elementtree import ElementTree

global log
global config


def setup_logging():
    """creates and returns stderr logger"""
    global log

    log = logging.getLogger()
    hdlr = logging.StreamHandler()
    form = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
    hdlr.setFormatter(form)
    log.addHandler(hdlr)
    log.setLevel(logging.WARN)


def parse_args():
    """Parses the command line arguments"""
    global log

    try:
        opts, args = getopt.getopt(sys.argv[1:], "c:hv",
                                   ["config", "help", "verbose"])
    except getopt.GetoptError as e:
        log.error("While parsing arguments: %s." % str(e).strip())
        usage()
        sys.exit(1)
    for opt, arg in opts:
        if opt == "-c" or opt == "--config":
            read_config(arg)
        elif opt == "-h" or opt == "--help":
            usage()
            sys.exit()
        elif opt == "-v" or opt == "--verbose":
            log.setLevel(log.debug)


def read_config(config_file):
    """Configuration file reader


    Reads the configuration from the given file

    and asserts the correctness and logical consistency
    of the content.
    """
    global config

    config = {}
    config_parser = ConfigParser.ConfigParser()
    # First, check whether the configuration file is corrupt overall
    try:
        config_parser.read(config_file)
    except ConfigParser.ParsingError as e:
        log.error("Configuration file '%s' contains errors." % config_file)
        log.error(str(e))
        sys.exit(1)
    # Then, check for mandatory parameters.
    # EGI and manual have to be set, else we can't continue
    try:
        for parameter in ['EGI', 'manual']:
            try:
                config[parameter] = config_parser.getboolean('configuration',
                                                             parameter)
            except ValueError:
                log.error("The value for parameter '%s' is not a boolean"
                          % parameter)
                sys.exit(1)
        for parameter in ['output_file', 'cache_dir',
                          'certification_status']:
            config[parameter] = config_parser.get('configuration', parameter)
        for parameter in ['cafile', 'capath']:
            if config_parser.has_option('configuration', parameter):
                config[parameter] = config_parser.get('configuration',
                                                      parameter)
            else:
                config[parameter] = None
    except ConfigParser.NoSectionError as e:
        log.error(("Missing section 'configuration' in"
                  " the configuration file %s.")
                  % config_file)
        sys.exit(1)
    except ConfigParser.NoOptionError as e:
        log.error("Missing parameter '%s' in the configuration file %s."
                  % (parameter, config_file))
        sys.exit(1)
    # If you do set manual = True , you're gonna need a manual_file
    if config['manual']:
        try:
            config_parser.get('configuration', 'manual_file')
        except ConfigParser.NoOptionError as e:
            log.error("You have specified manual configuration, but no manual_file\
                       in the %s configuration file" % config_file)
        sys.exit(1)


def usage():
    """prints the command line options of the program"""

    print("""
            Usage:""", os.path.basename(sys.argv[0]), """[options]

            Options:
              -c --config  Configuration File
              -h --help    Display this help
              -v --verbose Run in verbose mode

            """)


def get_url_data(url):
    # python urllib2 introduced server certificate validation starting
    # with version 2.7.9 and 3.4 (backported also e.g. to CentOS7). It
    # is no longer possible to download HTTPS data without having server
    # CA certificate in trusted store or explicitely disable verification.
    if hasattr(ssl, 'create_default_context'):
        capath = config.get('capath')
        cafile = config.get('cafile')
        if capath is not None or cafile is not None:
            context = ssl.create_default_context(cafile=cafile, capath=capath)
        else:
            context = ssl._create_unverified_context()
        return urllib2.urlopen(url, context=context).read()
    else:
        # older python versions doesn't really verify server certificate
        return urllib2.urlopen(url).read()


def get_egi_urls(status):

    egi_goc_url = ("https://goc.egi.eu/gocdbpi/public/"
                   "?method=get_site_list&certification_status=%s"
                   "&production_status=Production") % status

    try:
        response = get_url_data(egi_goc_url)
    except Exception as e:
        log.error("unable to get GOCDB Production %s sites: %s" % (status,
                  str(e)))
        return ""

    root = ElementTree.XML(response)
    egi_urls = {}
    for node in root:
        if not node.attrib['ROC'] in egi_urls.keys():
            egi_urls[node.attrib['ROC']] = []
        egi_urls[node.attrib['ROC']].append((node.attrib['NAME'],
                                            node.attrib['GIIS_URL']))

    return egi_urls


def create_urls_file(egi_urls):

    now = time.asctime()
    header = """#
# Top Level BDII configuration file
# ---------------------------------
# created on %s
#
# This file is generated, DO NOT EDIT it directly
#
""" % now

    if not os.path.exists(os.path.dirname(config['output_file'])):
        log.error("Output directory '%s' does not exist." %
                  config['output_file'])
        sys.exit(1)

    file_handle = open(config['output_file'] + ".tmp", 'w')
    file_handle.write(header)

    if egi_urls:
        for region in egi_urls:
            file_handle.write("\n#\n# %s\n# -----------\n#\n" % region)
            for site in egi_urls[region]:
                file_handle.write("\n#%s\n" % site[0])
                file_handle.write("%s %s\n" % site)

    if config['manual']:
        if os.path.exists(config['manual_file']):
            contents = open(config['manual_file']).read()
            file_handle.write("\n\n# Appended Manual Additions\n\n")
            file_handle.write(contents)
        else:
            log.error("Manual URL file %s does not exist!" %
                      config['manual_file'])
            sys.exit(1)

    file_handle.close()
    os.rename(config['output_file'] + ".tmp", config['output_file'])


if __name__ == "__main__":

    global config
    setup_logging()
    config = None
    parse_args()

    if not config:
        log.error("No configuration file given.")
        usage()
        sys.exit(1)

    egi_urls = None
    if config['EGI']:
        if not config['certification_status'] in ["Candidate", "Uncertified",
                                                  "Certified", "Closed",
                                                  "Suspended"]:
            message = "'%s' is not a valid certification_status." \
                % config['certification_status']
            log.error(message)
            sys.exit(1)
        egi_urls = get_egi_urls(config['certification_status'])
        pickle_file = config['cache_dir'] + '/' + 'EGI.pkl'
        if egi_urls != "" and len(egi_urls) > 0:
            file_handle = open(pickle_file, 'wb')
            pickle.dump(egi_urls, file_handle)
            file_handle.close()
        else:
            log.warn(("EGI GOCDB could not be contacted or returned no"
                      " information about EGI sites."
                      " Using cache file for EGI URLs."))
            file_handle = open(pickle_file, 'rb')
            egi_urls = pickle.load(file_handle)
            file_handle.close()

    create_urls_file(egi_urls)
