from __future__ import unicode_literals

import collections

from mopidy.config import types

# TODO: 2.6 cleanup (#344).
ordered_dict = getattr(collections, 'OrderedDict', dict)


def _did_you_mean(name, choices):
    """Suggest most likely setting based on levenshtein."""
    if not choices:
        return None

    name = name.lower()
    candidates = [(_levenshtein(name, c), c) for c in choices]
    candidates.sort()

    if candidates[0][0] <= 3:
        return candidates[0][1]
    return None


def _levenshtein(a, b):
    """Calculates the Levenshtein distance between a and b."""
    n, m = len(a), len(b)
    if n > m:
        return _levenshtein(b, a)

    current = xrange(n + 1)
    for i in xrange(1, m + 1):
        previous, current = current, [i] + [0] * n
        for j in xrange(1, n + 1):
            add, delete = previous[j] + 1, current[j - 1] + 1
            change = previous[j - 1]
            if a[j - 1] != b[i - 1]:
                change += 1
            current[j] = min(add, delete, change)
    return current[n]


class ConfigSchema(object):
    """Logical group of config values that correspond to a config section.

    Schemas are set up by assigning config keys with config values to
    instances. Once setup :meth:`deserialize` can be called with a dict of
    values to process. For convienience we also support :meth:`format` method
    that can used for converting the values to a dict that can be printed and
    :meth:`serialize` for converting the values to a form suitable for
    persistence.
    """
    # TODO: Use collections.OrderedDict once 2.6 support is gone (#344)
    def __init__(self, name):
        self.name = name
        self._schema = {}
        self._order = []

    def __setitem__(self, key, value):
        if key not in self._schema:
            self._order.append(key)
        self._schema[key] = value

    def __getitem__(self, key):
        return self._schema[key]

    def deserialize(self, values):
        """Validates the given ``values`` using the config schema.

         Returns a tuple with cleaned values and errors."""
        errors = {}
        result = {}

        for key, value in values.items():
            try:
                result[key] = self._schema[key].deserialize(value)
            except KeyError:  # not in our schema
                errors[key] = 'unknown config key.'
                suggestion = _did_you_mean(key, self._schema.keys())
                if suggestion:
                    errors[key] += ' Did you mean %s?' % suggestion
            except ValueError as e:  # deserialization failed
                result[key] = None
                errors[key] = str(e)

        for key in self._schema:
            if key not in result and key not in errors:
                result[key] = None
                errors[key] = 'config key not found.'

        return result, errors

    def serialize(self, values, display=False):
        """Converts the given ``values`` to a format suitable for persistence.

        If ``display`` is :class:`True` secret config values, like passwords,
        will be masked out.

        Returns a dict of config keys and values."""
        result = ordered_dict()  # TODO: 2.6 cleanup (#344).
        for key in self._order:
            if key in values:
                result[key] = self._schema[key].serialize(values[key], display)
        return result


class LogLevelConfigSchema(object):
    """Special cased schema for handling a config section with loglevels.

    Expects the config keys to be logger names and the values to be log levels
    as understood by the :class:`LogLevel` config value. Does not sub-class
    :class:`ConfigSchema`, but implements the same interface.
    """
    def __init__(self, name):
        self.name = name
        self._config_value = types.LogLevel()

    def deserialize(self, values):
        errors = {}
        result = {}

        for key, value in values.items():
            try:
                result[key] = self._config_value.deserialize(value)
            except ValueError as e:  # deserialization failed
                result[key] = None
                errors[key] = str(e)
        return result, errors

    def serialize(self, values, display=False):
        result = ordered_dict()  # TODO: 2.6 cleanup (#344)
        for key in sorted(values.keys()):
            result[key] = self._config_value.serialize(values[key], display)
        return result

@@ -91,94 +91,8 @@
 es%0A%0A
-# TODO: 2.6 cleanup (#344).%0Aordered_dict = getattr(collections, 'OrderedDict', dict)%0A%0A
 %0Adef
@@ -969,36 +969,53 @@
 ss ConfigSchema(
-obje
+collections.OrderedDi
 ct):%0A    %22%22%22Logi
@@ -1484,393 +1484,103 @@
     
-# TODO: Use collections.OrderedDict once 2.6 support is gone (#344)%0A    def __init__(self, name):%0A        self.name = name%0A        self._schema = %7B%7D%0A       
+def __init__(self, name):%0A        super(ConfigSchema,
  self
+)
 ._
-order = %5B%5D%0A%0A    def __setitem__(self, key, value):%0A        if key not in self._schema:%0A            self._order.append(key)%0A        self._schema%5Bkey%5D = value%0A%0A    def __getitem__(self, key):%0A        return self._schema%5Bkey%5D
+_init__()%0A        self.name = name
 %0A%0A  
@@ -1870,32 +1870,24 @@
 %5Bkey%5D = self
-._schema
 %5Bkey%5D.deseri
@@ -2058,16 +2058,8 @@
 elf.
-_schema.
 keys
@@ -2320,23 +2320,22 @@
 in self.
-_schema
+keys()
 :%0A      
@@ -2814,51 +2814,33 @@
 t = 
-o
+collections.O
 rdered
-_d
+D
 ict()
-  # TODO: 2.6 cleanup (#344).
 %0A   
@@ -2864,14 +2864,14 @@
 elf.
-_order
+keys()
 :%0A  
@@ -2936,16 +2936,8 @@
 self
-._schema
 %5Bkey
@@ -3311,16 +3311,42 @@
 he same 
+serialize/deserialize%0A    
 interfac
@@ -3895,50 +3895,33 @@
 t = 
-o
+collections.O
 rdered
-_d
+D
 ict()
-  # TODO: 2.6 cleanup (#344)
 %0A   

Switch to ordered dict