from __future__ import unicode_literals
from collections import namedtuple
import logging

import utils
from py2to3 import string_types

from xvalidator.validators import Validator, ValidationException, NCName, Name


logger = logging.getLogger(__name__)

KeyRef = namedtuple('KeyRef', 'key_name key_value ref_path')


class KeyStore(object):
    def __init__(self):
        self._key_index = {}
        self._keys = {}

    def add_key(self, key_names, target_path):
        if isinstance(key_names, list):
            key_names_list = key_names
        else:
            key_names_list = [key_names]
        for key_name in key_names_list:
            key = '%s:%s' % (key_name, target_path)
            if key in self._keys:
                raise ValidationException('Key %s does already exist.' % key,
                                          target_path)
            if not key_name in self._key_index:
                self._key_index[key_name] = [target_path]
            else:
                self._key_index[key_name].append(target_path)
            self._keys[key] = {}

    def in_keys(self, key_name, target_path):
        return '%s:%s' % (key_name, target_path) in self._keys

    def add_value(self, key_names, target_path, key_value, key_path):
        if isinstance(key_names, string_types):
            key_names_list = [key_names]
        else:
            key_names_list = key_names
        for key_name in key_names_list:
            key = '%s:%s' % (key_name, target_path)
            if self.in_keys(key_name, target_path):
                if key_value in self._keys[key]:
                    msg = 'Duplicate key value %s for %s at %s' % (key_value,
                                                                   key_name,
                                                                   key_path)
                    raise ValidationException(msg, key_value)
                self._keys[key][key_value] = key_path
                return True
        msg = 'Could not find target path %s for key name(s) %s' % (target_path,
                                                                    ', '.join(key_names_list))
        raise ValidationException(msg, key_value)

    def match_ref(self, key_name, ref_key_value):
        if not key_name in self._key_index:
            raise ValidationException('No key for %s exists' % key_name,
                                      key_name)
        for key_path in self._key_index[key_name]:
            key = '%s:%s' % (key_name, key_path)
            for key_value, instance_path in self._keys[key].items():
                if key_value == ref_key_value:
                    return instance_path
        raise ValidationException('Could not match ref %s for %s' % (
            ref_key_value, key_name), ref_key_value)

    def key_value_count(self, key_name, target_path):
        key = '%s:%s' % (key_name, target_path)
        if key in self._keys:
            return len(self._keys[key])
        return 0

    @property
    def keys(self):
        return {key: value for key, value in self._keys.items()}


class IDStore(KeyStore):
    key_name = 'ID'
    path = '/'

    def __init__(self):
        super(IDStore, self).__init__()
        super(IDStore, self).add_key(self.key_name, self.path)

    def add_id(self, key_value, key_path):
        super(IDStore, self).add_value(self.key_name, self.path,
                                       key_value, key_path)

    def match_id(self, ref_key_value):
        return super(IDStore, self).match_ref(self.key_name, ref_key_value)

    def id_count(self):
        return super(IDStore, self).key_value_count(self.key_name, self.path)


class RefStore(object):
    def __init__(self):
        self._refs = []
        self._targets = {}

    def add_key_ref(self, key_name, key_value, ref_path):
        if not key_value:
            raise ValidationException('key value is required', key_value)
        self._refs.append(KeyRef(key_name, key_value, ref_path))

    def set_target(self, ref_path, target_path):
        if ref_path in self._targets:
            raise ValidationException('Target for ref_path already exists.', ref_path)
        self._targets[ref_path] = target_path

    @property