# coding=utf-8
"""
Useful classes borrowed from google.com
"""
__author__ = 'talpah@gmail.com'

import os
import re
import socket
from datetime import datetime
from datetime import timedelta

import yaml
from bottle import request, response, PluginError


class Configuration(object):
    def __init__(self, filename):
        self._filename = filename
        self.containers = []
        try:
            with open(filename) as stream:
                self.__dict__.update(yaml.load(stream))
        except IOError:
            self.save()

    def save(self):
        with open(self._filename, "w+") as stream:
            yaml.dump(self.items(), stream)

    def __iter__(self):
        for key in self.__dict__:
            if key[0] != "_":
                yield key

    def __getitem__(self, item):
        return getattr(self, item)

    def __setitem__(self, key, value):
        return setattr(self, key, value)

    def __delitem__(self, key):
        return delattr(self, key)

    def items(self):
        return {x: self[x] for x in self}


class FlashMsgPlugin(object):
    """Usage:
    from bottle import Bottle
    from bottle_flash2 import FlashMsgPlugin

    app = Bottle()
    COOKIE_SECRET = 'your_secret_key'
    app.install(FlashPlugin(secret=COOKIE_SECRET))

    @route('/logout', name='logout')
    def logout():
        app.flash('Logged out successfully !')
        redirect(url('login'))

    . To consume the flashed messages,
    . you would do something like the following (jinja2).
    . Here I am assuming that I get the "app" in the template context.

    {% set messages = app.get_flashed_messages() %}
    {% if messages %}
    <div id="flash_messages">
      <ul>
          {% for m in messages %}
          <li>{{ m[0] }}</li>
          {% endfor %}
      </ul>
    </div>
    {% endif %}

    . This can be achieved with the bottle_renderer_ plugin,
    . or by adding to the defaults dict like this..

    Jinja2Template.defaults = {'app': app}

    . placing the app in all template contexts.

    """
    name = 'flash'
    api = 2

    def __init__(self, key=name, secret=None):
        self.key = key
        self.app = None
        self.secret = secret

    def setup(self, app):
        """ Make sure that other installed plugins don't affect the same
        keyword argument and check if metadata is available."""
        for other in app.plugins:
            if not isinstance(other, FlashMsgPlugin):
                continue
            if other.keyword == self.keyword:
                raise PluginError("Found another flashmsg plugin "
                                  "with conflicting settings ("
                                  "non-unique keyword).")
        self.app = app
        self.app.add_hook('before_request', self.load_flashed)
        self.app.add_hook('after_request', self.set_flashed)
        self.app.flash = self.flash
        self.app.get_flashed_messages = self.get_flashed_messages

    def load_flashed(self):
        m = request.get_cookie(self.key, secret=self.secret)
        if m is not None:
            response.flash_messages = m

    def set_flashed(self):
        if hasattr(response, 'flash_messages'):
            return response.set_cookie(self.key,
                                       response.flash_messages,
                                       self.secret)

    def flash(self, message, level=None):
        if not hasattr(response, 'flash_messages'):
            response.flash_messages = []
        response.flash_messages.append((message, level))

    def get_flashed_messages(self):
        if hasattr(response, 'flash_messages'):
            m = response.flash_messages
            response.delete_cookie(self.key)
            delattr(response, 'flash_messages')
            return m

    def apply(self, callback, context):
        return callback


def ip_to_ints(ip):
    return map(int, ip.split('.'))


def compare_ip(ip1, ip2):
    """Comparator function for comparing two IPv4 address strings"""
    return (ip_to_ints(ip1) > ip_to_ints(ip2)) - (ip_to_ints(ip1) < ip_to_ints(ip2))


def get_created_comment():
    return '\n'.join([
        '# Autogenerated by hosts.py',
        '# https://github.com/mdomi/hosts',
        '# Updated: %s' % datetime.now()
    ])


class Hosts(object):
    """
    Hosts file manager
    :param path:
    """

    def __init__(self, path):
        self.hosts = {}
        self.read(path)

    def get_reversed(self):
        reversed_hosts = {}
        for host_name in self.hosts.keys():
            ip_address = self.hosts[host_name]
            if ip_address not in reversed_hosts:
                reversed_hosts[ip_address] = [host_name]
            else:
                reversed_hosts[ip_address].append(host_name)
        return reversed_hosts

    def get_one(self, host_name, raise_on_not_found=False):
        if host_name in self.hosts:
            return self.hosts[host_name]
        try:
            socket.gethostbyname(host_name)
        except socket.gaierror:
            if raise_on_not_found:
                raise Exception('Unknown host: %s' % (host_name,))
        return '[Unknown]'

    def file_contents(self):
        reversed_hosts = {}
        for host_name in self.hosts.keys():
            ip_address = self.hosts[host_name]
            if ip_address not in reversed_hosts:
                reversed_hosts[ip_address] = [host_name]
            else:
                reversed_hosts[ip_address].append(host_name)
        parts = []
        for ip_address in sorted(reversed_hosts.keys()):
            # parts.append('\n# -- %s -- #' % (ip_address,))
            parts.append('%s\t%s' % (ip_address, " ".join(sorted(reversed_hosts[ip_address]))))
            # parts.append('# -- %s -- #' % (ip_address,))
        return '\n'.join(parts)

    def load(self, contents):
        for line in contents.split('\n'):
            if len(re.sub('\s*', '', line)) and not line.startswith('#'):
                parts = re.split('\s+', line)
                ip_address = parts[0]
                for host_name in parts[1:]:
                    self.hosts[host_name] = ip_address

    def read(self, path):
        """Read the hosts file at the given location and parse the contents"""
        if not os.path.isfile(path):
            self.load(path)
        else:
            with open(path, 'r') as hosts_file:
                self.load(hosts_file.read())

    def remove_one(self, host_name):
        """Remove a mapping for the given host_name"""
        if host_name in self.hosts:
            del self.hosts[host_name]

    def remove_all(self, host_names):
        """Remove a mapping for the given host_name"""
        for host_name in host_names:
            self.remove_one(host_name)

    def write(self, path):
        """Write the contents of this hosts definition to the provided path"""
        contents = self.file_contents()
        with open(path, 'w') as hosts_file:
            hosts_file.write(contents)

    def set_one(self, host_name, ip_address):
        """Set the given hostname to map to the given IP address"""
        self.hosts[host_name] = ip_address

    def set_all(self, host_names, ip_address):
        """Set the given list of hostnames to map to the given IP address"""
        for host_name in host_names:
            self.set_one(host_name, ip_address)

    def alias_all(self, host_names, target):
        """Set the given hostname to map to the IP address that target maps to"""
        self.set_all(host_names, self.get_one(target, raise_on_not_found=True))


def group_containers_by_name(container_list):
    clist = {}
    for container in container_list:
        container_segments = container['Name'].split('_')
        container_segments[0] = container_segments[0][1:]
        if len(container_segments) == 3:
            container_group = container_segments[0]
            container_name = container_segments[1]
            container_count = container_segments[2]
        elif container_segments[-1].isdigit():
            container_group = container_segments[0]
            container_name = '_'.join(container_segments[1:-1])
            container_count = container_segments[-1]
        else:
            container_group = container_segments[0]
            container_name = '_'.join(container_segments)
            container_count = None

        container['_Name'] = container_name
        container['_Group'] = container_group
        container['_Count'] = container_count

        if container_group in clist:
            clist[container_group].append(container)
        else:
            clist[container_group] = [container]
    return clist


def delta2dict(delta):
    """Accepts a delta, returns a dictionary of units"""
    delta = abs(delta)
    return {
        'year': int(delta.days / 365),
        'day': int(delta.days % 365),
        'hour': int(delta.seconds / 3600),
        'minute': int(delta.seconds / 60) % 60,
        'second': delta.seconds % 60,
        'microsecond': delta.microseconds
    }


def human(dt, precision=2, past_tense='{} ago', future_tense='in {}'):
    """Accept a datetime or timedelta, return a human readable delta string"""
    delta = dt
    if type(dt) is not type(timedelta()):
        delta = datetime.now() - dt

    the_tense = past_tense
    if delta < timedelta(0):
        the_tense = future_tense

    d = delta2dict(delta)
    hlist = []
    count = 0
    units = ('year', 'day', 'hour', 'minute', 'second', 'microsecond')
    for unit in units:
        if count >= precision: break  # met precision
        if d[unit] == 0:
            continue  # skip 0's
        s = '' if d[unit] == 1 else 's'  # handle plurals
        hlist.append('%s %s%s' % (d[unit], unit, s))
        count += 1
    human_delta = ', '.join(hlist)
    return the_tense.format(human_delta)
