# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
#
# This library is free software: you can redistribute it and/or modify it 
# under the terms of the GNU Lesser General Public License as published by 
# the Free Software Foundation, either version 3 of the License, or 
# (at your option) any later version. This library is distributed in the 
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
# See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.

import logging

import mutils
from collections import OrderedDict

__all__ = [
    "matchNames",
    "groupObjects",
]


logger = logging.getLogger(__name__)


def rotateSequence(seq, current):
    """
    :type seq:
    :type current:
    :rtype:
    """
    n = len(seq)
    for i in range(n):
        yield seq[(i + current) % n]


def groupObjects(objects):
    """
    :type objects:
    :rtype:
    """
    results = OrderedDict()
    for name in objects:
        node = mutils.Node(name)
        results.setdefault(node.namespace(), [])
        results[node.namespace()].append(name)
    results = OrderedDict(sorted(results.items(), key=lambda t: (t[0].count(":"), len(t[0]))))
    return results


def indexObjects(objects):
    """
    :type objects: list[str]
    :rtype: dict
    """
    result = {}
    if objects:
        for name in objects:
            node = mutils.Node(name)
            result.setdefault(node.shortname(), [])
            result[node.shortname()].append(node)
    return result


def matchInIndex(node, index):
    """
    :type node: mutils.Node
    :type index: dict[list[mutils.Node]]
    :rtype: Node
    """
    result = None
    if node.shortname() in index:
        nodes = index[node.shortname()]
        if nodes:
            for n in nodes:
                if node.name().endswith(n.name()) or n.name().endswith(node.name()):
                    result = n
                    break
        if result is not None:
            index[node.shortname()].remove(result)

    return result


def matchNames(srcObjects, dstObjects=None, dstNamespaces=None, search=None, replace=None):
    """
    :type srcObjects: list[str]
    :type dstObjects: list[str]
    :type dstNamespaces: list[str]
    :rtype: list[(mutils.Node, mutils.Node)]
    """
    results = []
    if dstObjects is None:
        dstObjects = []

    srcGroup = groupObjects(srcObjects)
    srcNamespaces = srcGroup.keys()

    if not dstObjects and not dstNamespaces:  # and not selection:
        dstNamespaces = srcNamespaces

    if not dstNamespaces and dstObjects:
        dstGroup = groupObjects(dstObjects)
        dstNamespaces = dstGroup.keys()

    dstIndex = indexObjects(dstObjects)
    # DESTINATION NAMESPACES NOT IN SOURCE OBJECTS
    dstNamespaces2 = [x for x in dstNamespaces if x not in srcNamespaces]

    # DESTINATION NAMESPACES IN SOURCE OBJECTS
    dstNamespaces1 = [x for x in dstNamespaces if x not in dstNamespaces2]

    # CACHE DESTINATION OBJECTS WITH NAMESPACES IN SOURCE OBJECTS
    usedNamespaces = []
    notUsedNamespaces = []

    # FIRST LOOP THROUGH ALL DESTINATION NAMESPACES IN SOURCE OBJECTS
    for srcNamespace in srcNamespaces:
        if srcNamespace in dstNamespaces1:
            usedNamespaces.append(srcNamespace)
            for name in srcGroup[srcNamespace]:

                srcNode = mutils.Node(name)

                if search is not None and replace is not None:
                    # Using the mirror table which supports * style replacing
                    name = mutils.MirrorTable.replace(name, search, replace)

                dstNode = mutils.Node(name)

                if dstObjects:
                    dstNode = matchInIndex(dstNode, dstIndex)
                if dstNode:
                    results.append((srcNode, dstNode))
                    yield (srcNode, dstNode)
                else:
                    logger.debug("Cannot find matching destination object for %s" % srcNode.name())
        else:
            notUsedNamespaces.append(srcNamespace)

    # SECOND LOOP THROUGH ALL OTHER DESTINATION NAMESPACES
    srcNamespaces = notUsedNamespaces
    srcNamespaces.extend(usedNamespaces)
    _index = 0
    for dstNamespace in dstNamespaces2:
        match = False
        i = _index
        for srcNamespace in rotateSequence(srcNamespaces, _index):
            if match:
                _index = i
                break
            i += 1
            for name in srcGroup[srcNamespace]:
                srcNode = mutils.Node(name)
                dstNode = mutils.Node(name)
                dstNode.setNamespace(dstNamespace)

                if dstObjects:
                    dstNode = matchInIndex(dstNode, dstIndex)
                elif dstNamespaces:
                    pass

                if dstNode:
                    match = True
                    results.append((srcNode, dstNode))
                    yield (srcNode, dstNode)
                else:
                    logger.debug("Cannot find matching destination object for %s" % srcNode.name())

    if logger.parent.level == logging.DEBUG or logger.level == logging.DEBUG:
        for dstNodes in dstIndex.values():
            for dstNode in dstNodes:
                logger.debug("Cannot find matching source object for %s" % dstNode.name())
