from collections import OrderedDict
from copy import copy
from floppy.FloppyTypes import Type, MetaType
from threading import Lock
from os.path import isfile
import floppy.graph

NODECLASSES = {}
_NODECLASSES = {}
# STOREDVALUES = {}


class InputNotAvailable(Exception):
    pass


class InputAlreadySet(Exception):
    pass


def abstractNode(cls: type):
    """
    Removes a Node class from the NODECLASSES dictionary and then returns the class object.
    Use this as a decorator to stop a not fully implemented Node class from being available in the editor.
    :param cls: Node class object.
    :return: Unmodified node class object.
    :rtype: Node
    """
    del NODECLASSES[cls.__name__]
    return cls


def Input(*args, **kwargs):
    pass


def Output(*args, **kwargs):
    pass


def Tag(*args, **kwargs):
    pass


class Info(object):
    """
    Class for handling all information related to both inputs and outputs.
    """
    def __init__(self, name, varType, hints=None, default='', select=None, owner=False, list=False, optional=False):
        self.multiConn = 0
        self.multiCounter = 0
        self.name = name
        self.connected = False
        self.varType = varType
        self.optional = optional
        if not hints:
            self.hints = [varType.__name__]
        else:
            self.hints = [varType.__name__] + hints
        self.default = default
        self.valueSet = False
        self.value = None
        self.select = select
        self.owner = owner
        self.list = list
        self.loopLevel = 0
        self.usedDefault = False
        self.pure = 0

    def setOwner(self, owner):
        self.owner = owner

    def setDefault(self, value):
        if not self.varType == object and not issubclass(self.varType, Type):
            try:
                self.default = self.varType(value)
            except ValueError:
                self.default = ''
            if self.varType == bool:
                try:
                    if value.upper() == 'TRUE':
                        self.default = True
                    else:
                        self.default = False
                except:
                    self.default = value
        else:
            self.default = value

    def __str__(self):
        return 'INFO'

    def reset(self, nodeLoopLevel=0, force=False):
        if nodeLoopLevel > self.loopLevel and not force:
            # print('Not resetting Input {} because owing node has higher node\n'
            #       'level than the node setting the Input: {}vs.{}'.format(self.name, nodeLoopLevel, self.loopLevel))
            return
        self.default = None
        self.valueSet = False
        self.value = None
        self.multiCounter = 0


class InputInfo(Info):
  
  def __call__(self, noException=False):
        if self.valueSet:
            if not self.varType == object:
                if isinstance(self.varType, MetaType):
                    if self.list:
                        return [self.varType.checkType(i) for i in self.value]
                    return self.varType.checkType(self.value)
                else:
                    if self.list:
                        return [self.varType(i) for i in self.value]
                    return self.varType(sel
f.value)
            else:
                return self.value
        elif self.default != None and not self.connected:
            self.usedDefault = True if self.loopLevel > 0 else False
            if not self.varType == object and self.default:
                return self.varType(self.default)
            else:
                return self.default
        else:
            if noException:
                return None
            else:
                raise InputNotAvailable('Input not set for node.')

    def set(self, value, override=False, loopLevel=0):
        if self.valueSet and not override:
            raise InputAlreadySet('Input \'{}\' of node \'{}\' is already set.'.format(self.name, str(self.owner)))
        self.value = value
        self.valueSet = True
        if not self.name == 'Control':
            self.loopLevel = loopLevel
        else:
            self.multiCounter += 1

    def setPure(self):
        self.pure = 1

    def setConnected(self, value: bool):
        self.connected = value

    def setMultiConn(self, value):
        self.multiConn = value

    def isAvailable(self, info=False):
        # if self.owner.__class__.__name__.startswith('GetValue'):
        #     print()
        #     print(self.name)
        #     print(self.default, self.connected, self.usedDefault, self.pure, self.loopLevel)
        #     print(self.loopLevel)
        if self.name == 'Control':
            return self._isAvailableControl(info)
        if info:
            if self.valueSet:
                return True
            elif self.default != None and not self.connected and not self.usedDefault and self.pure < 2:
                return True
            return False
        # print(1)
        if self.valueSet:
            # print(2)
            # print('^^^^^^^^^^^^^^^^^^', self.name, self.value, self.valueSet)
            return True
        elif self.default != None and not self.connected and not self.usedDefault and self.pure < 2:
            # print(3)
            if self.pure == 1 and self.connected:
                self.pure = 2
            # print(4)
            # self.usedDefault = True
            # print('+++++++++++++++++', self.name, self.value, self.valueSet, self.owner, self.usedDefault, self.pure)
            return True
        # print(self.default, self.connected, self.usedDefault, self.pure, self.loopLevel)
        return False

    def _isAvailableControl(self, info):
        if self.multiCounter < self.multiConn:
            return False
        if info:

            if self.valueSet:
                return True
            elif self.default != None and not self.connected and not self.usedDefault and self.pure < 2:
                return True
            return False
        if self.valueSet:
            # print('^^^^^^^^^^^^^^^^^^', self.name, self.value, self.valueSet)
            return True
        elif self.default != None and not self.connected and not self.usedDefault and self.pure < 2:
            if self.pure == 1:
                self.pure = 2
            # self.usedDefault = True
            # print('+++++++++++++++++', self.name, self.value, self.valueSet, self.owner, self.usedDefault, self.pure)
            return True
        return False


class OutputInfo(Info):
    def __call__(self, value):
        try:
            value.__FloppyType__ = self.varType
        except AttributeError:
            pass
        self.value = value
        self.valueSet = True




class MetaNode(type):
    """
    Meta class for the Node class. Makes node declaration objects available in the class's scope and registers each
    Node object to have a convenient way of accessing all subclasses of Node.
    """
    inputs = []
    outputs = []

    @classmethod
    def __prepare__(metacls, name, bases):
        MetaNode.inputs = []
        MetaNode.outputs = []
        MetaNode.tags = []
        return {'Input': MetaNode.addInput,
                'input': MetaNode.addInput,
                'Output': MetaNode.addOutput,
                'output': MetaNode.addOutput,
                'Tag': MetaNode.addTag,
                'tag': MetaNode.addTag,}

    def addTag(*args):
        for arg in args:
            MetaNode.tags.append(arg)


    def addInput(name: str,
                 varType: object,
                 hints=None,
                 default='',
                 select=None,
                 list=False,
                 optional=False):
        MetaNode.inputs.append({'name': name,
                                'varType': varType,
                                'hints': hints,
                                'default': default,
                                'select': select,
                                'list': list,
                                'optional': optional})

    def addOutput(name: str,
                  varType: object,
                  hints=None,
                 