#!/usr/bin/env python
# -*- coding: utf-8 -*-

import brewery.utils as utils
import heapq

__all__ = (
    "create_node",
    "node_dictionary",
    "node_catalogue",
    "get_node_info",
    "NodeFinished",
    "Node",
    "SourceNode",
    "TargetNode",
    "Stack"
)

# FIXME: temporary dictionary to record displayed warnings about __node_info__
_node_info_warnings = set()

def create_node(identifier, *args, **kwargs):
    """Creates a node of type specified by `identifier`. Options are passed to
    the node initializer"""

    d = node_dictionary()
    node_class = d[identifier]
    node = node_class(*args, **kwargs)
    return node

def node_dictionary():
    """Return a dictionary containing node name as key and node class as
    value. This will be depreciated soon in favour of
    :func:`node_catalogue()`"""

    classes = node_subclasses(Node)
    dictionary = {}

    for c in classes:
        try:
            name = c.identifier()
            dictionary[name] = c
        except AttributeError:
            # If node does not provide identifier, we consider it to be
            # private or abstract class
            pass

    return dictionary

def node_catalogue():
    """Returns a dictionary of information about all available nodes. Keys are
    node identifiers, values are dictionaries. The information dictionary contains
    all the keys from the node's `node_info` dictionary plus keys: `factory`
    with node class, `type` (if not provided) is set to one of ``source``,
    ``processing`` or ``target``.
    """

    classes = node_subclasses(Node)

    catalogue = {}

    for node_class in classes:
        try:
            name = node_class.identifier()
        except AttributeError:
            # If node does not provide identifier, we consider it to be
            # private or abstract class
            continue

        # Get copy of node info
        info = dict(get_node_info(node_class))
        info["name"] = name
        info["factory"] = node_class

        # Get node type based on superclass, if not provided

        if "type" not in info:
            if issubclass(node_class, SourceNode):
                info["type"] = "source"
            elif not issubclass(node_class, SourceNode) \
                    and not issubclass(node_class, TargetNode):
                info["type"] = "processing"
            elif issubclass(node_class, TargetNode):
                info["type"] = "target"
            else:
                info["type"] = "unknown"

        catalogue[name] = info

    return catalogue

def node_subclasses(root, abstract = False):
    """Get all subclasses of node.

    :Parameters:
        * `abstract`: If set to ``True`` all abstract classes are included as well. Default is
          ``False``
    """
    classes = []
    for c in utils.subclass_iterator(root):
        try:
            info = get_node_info(c)

            node_type = info.get("type")
            if node_type != "abstract":
                classes.append(c)
        except AttributeError:
            pass

    return classes

def get_node_info(cls):
    """Get node info attribute of a node - transient function during
    depreciation"""

    if hasattr(cls, "__node_info__") and cls not in _node_info_warnings:

        utils.get_logger().warn("depreciated __node_info__ present in %s, rename to node_info" \
                    " (this warning will be shown only once)" % str(cls))
        _node_info_warnings.add(cls)

        return cls.__node_info__
    else:
        return cls.node_info

class Stack(object):
    """A stack holding records from a pipe. Each record has a key. 
    At most `depth` records are stored based on their key order.
    """

    def __init__(self, depth):
        self.depth = depth
        self.heap = []