#!/usr/bin/env python

from __future__ import print_function
import os
import sys
import copy
import json
try:
    import configparser
except ImportError:
    import ConfigParser as configparser
import pprint
try:
    import xmlrpclib
except ImportError:
    import xmlrpc.client as xmlrpclib
import base64
import bz2
import socket

# globals
exclude_list = []


class HostConfig(object):
    """Holder for config info from the configuration file"""
    def __init__(self):
        self.config = {
            'version': 0,
            'global': {},
            'site': {},
            'host': {},
            'stats': {},
        }


def get_exclude_dir_patterns_from_file(options):
    global exclude_list
    if options.exclude_from:
        f = open(options.exclude_from, 'r')
        for line in f:
            line = line.strip()
            if line.startswith(u'#'):
                continue
            exclude_list.append(line)
        f.close()


def exclude_dirs(root, dirs):
    global exclude_list
    copydirs = copy.copy(dirs)
    # iterate over the copy so we can modify the original
    for d in copydirs:
        # keep trailing /, we know it's a directory
        testdir = os.path.join(root, d, '')
        for pattern in exclude_list:
            if pattern in testdir:
                dirs.remove(d)
                break


def gen_dirtree(path):
    # structure here is:
    # dirtree is a dict
    # {
    #   dirpath :
    #                {
    #                   filename1 : size1,
    #                   filename2 : size2,
    #                   ...
    #                 },
    #   ...
    # }
    #
    # 2009-03-09: MM's web app ignores the statfiles dict.  So don't bother
    # generating it.

    dirtree = {}
    for dirpath, dirnames, filenames in os.walk(path):
        exclude_dirs(dirpath, dirnames)
        statfiles = {}
        if path.endswith('/'):
            short_path = dirpath[len(path):]
        else:
            short_path = dirpath[len(path)+1:]
        if len(short_path) > 0:
            dirtree[short_path] = statfiles
        else:
            dirtree[''] = statfiles

    return dirtree


def errorprint(error):
    sys.stderr.write(error+'\n')


class MissingOption(Exception):
    pass


def check_required_options(conf, section, required_options):
    for o in required_options:
        if not conf.has_option(section, o):
            errorprint(
                'missing required option %s in config [%s]' % (o, section))
            raise MissingOption()
    return True


def parse_value(value):
    """Split multi-line values into a list"""
    if value.find('\n') > -1:
        return value.split()
    return value


def parse_section(conf, section, item, required_options,
                  optional_options=[]):
    if conf.has_option(section, 'enabled'):
        if conf.get(section, 'enabled') != '1' \
                and section.lower() in item.config:
            print('removing disabled section %s' % (section))
            del item.config[section.lower()]
            return False

    if not check_required_options(conf, section, required_options):
        return False

    if not section.lower() in item.config:
        item.config[section.lower()] = {}

    for o in required_options:
        item.config[section.lower()][o] = parse_value(
            conf.get(section, o))
    for o in optional_options:
        if conf.has_option(section, o):
            item.config[section.lower()][o] = parse_value(
                conf.get(section, o))

    return True


def parse_global(conf, section, item):
    required_options = ['enabled', 'server']
    if not parse_section(conf, section, item, required_options):
        errorprint(
            'missing required options (server AND enabled) in [%s] section'
            % (section))
        return False
    return True


def parse_site(conf, section, item):
    required_options = ['enabled', 'name', 'password']
    return parse_section(conf, section, item, required_options)


def parse_host(conf, section, item):
    required_options = ['enabled', 'name']
    optional_options = ['user_active']
    return parse_section(
        conf, section, item, required_options,
        optional_options=optional_options)


def get_stats(conf, section):
    if conf.has_option(section, 'enabled'):
        if conf.get(section, 'enabled') != '1':
            return None
    statsdata = {}
    for name, value in conf.items(section):
        if name == 'enabled':
            continue
        filenames = parse_value(conf.get(section, name))
        if type(filenames) != list:
            filenames = [filenames]
        for fn in filenames:
            try:
                f = open(fn, 'r')
                contents = contents + f.readlines()
                statsdata[name] = json.dumps(contents)
                f.close()
            except IOError:
                pass
    return statsdata


def parse_category(conf, section, item, crawl):
    required_options = ['enabled', 'path']
    if not parse_section(conf, section, item, required_options):
        return False

    if crawl:
        dirtree = gen_dirtree(conf.get(section, 'path'))
        item.config[section.lower()]['dirtree'] = dirtree
    # database doesn't need to know the disk path
    del item.config[section.lower()]['path']


def config(cfg, item, crawl=True):
    broken = False
    conf = configparser.ConfigParser()
    files = conf.read(cfg)
    if files == []:
        errorprint('Configuration file %s not found' % (cfg))
        return False
    conf.read(cfg)

    try:
        # don't grab parse_stats here
        for section, parsefunc in [
                ('global', parse_global),
                ('site', parse_site),
                ('host', parse_host)
                ]:
            if conf.has_section(section):
                if not parsefunc(conf, section, item):
                    return False
            else:
                errorprint(
                    'Invalid configuration - missing section [%s]'
                    % (section))
                sys.exit(1)

        for section in conf.sections():
            if section in ['global', 'site', 'host', 'stats']:
                continue
            parse_category(conf, section, item, crawl)

    except MissingOption:
        errorprint('Invalid configuration - Exiting')
        sys.exit(1)

    return True


options = None


def main():
    from optparse import OptionParser
    parser = OptionParser(usage=sys.argv[0] + " [options]")
    parser.add_option("-c", "--config",
                      dest="config",
                      default='/etc/mirrormanager-client/report_mirror.conf',
                      help='Configuration filename (required)')
#    parser.add_option("-s", "--stats",
#                      action="store_true",
#                      dest="stats",
#                      default=False,
#                      help='Send stats')
    parser.add_option("-i", "--input",
                      dest="input",
                      default=None,
                      help="Input filename (for debugging)")
    parser.add_option("-o", "--output",
                      dest="output",
                      default=None,
                      help="Output filename (for debugging)")
    parser.add_option("-n", "--no-send",
                      action="store_true",
                      dest="no_send",
                      default=False,
                      help="Don't send data to the server.")
    parser.add_option("-d", "--debug",
                      action="store_true",
                      dest="debug",
                      default=False,
                      help='Enable debugging output')
    parser.add_option("--exclude-from",
                      dest="exclude_from",
                      default=None,
                      help='get exclude patterns from FILE')

    (options, args) = parser.parse_args()
    item = HostConfig()
    get_exclude_dir_patterns_from_file(options)
    if options.input:
        infile = open(options.input, 'rb')
        item.config = json.load(infile)
        infile.close()
        if not config(options.config, item, crawl=False):
            sys.exit(1)
    else:
        if not config(options.config, item, crawl=True):
            sys.exit(1)

    p = json.dumps(item.config)

    if options.debug:
        pp = pprint.PrettyPrinter(indent=4)
        pp.pprint(item.config)

    if options.output is not None:
        outfile = open(options.output, 'wb')
        outfile.write(p)
        outfile.close()

#    if options.stats:
#        statdata = get_stats(conf, 'stats')

    # upload p and statsdata here
    if item.config.get('global', {}).get('enabled') != '1':
        sys.exit(1)

    if not options.no_send:
        #  print("Connecting to %s" % item.config['global']['server'])
        server = xmlrpclib.ServerProxy(item.config['global']['server'])

        data = base64.urlsafe_b64encode(bz2.compress(p.encode())).decode()

        if data is not None:
            try:
                print(server.checkin(data))
            except socket.error as m:
                print("Error checking in: %s.  Please try again later." % (
                    m[1]))
            except xmlrpclib.ProtocolError:
                print("Error checking in: Service Temporarily "
                      "Unavailable.  Please try again later.")
                sys.exit(1)
            except xmlrpclib.Fault:
                print("Error checking in.  Connection closed before "
                      "checkin complete.  Please try again later.")
                sys.exit(1)


if __name__ == '__main__':
    main()