# Copyright (c) 2012 CEF Python, see the Authors file.
# All rights reserved. Licensed under BSD 3-clause license.
# Project website: https://github.com/cztomczak/cefpython

include "cefpython.pyx"

cdef class JavascriptBindings:
    # By default binding only to top frame.
    cdef public py_bool bindToFrames
    cdef public py_bool bindToPopups
    cdef public dict functions
    cdef public dict properties
    cdef public dict objects

    def __init__(self, bindToFrames=False, bindToPopups=False):
        self.functions = {}
        self.properties = {}
        self.objects = {}

        self.bindToFrames = bool(bindToFrames)
        self.bindToPopups = bool(bindToPopups)

    cpdef py_bool GetBindToFrames(self):
        return bool(self.bindToFrames)

    cpdef py_bool GetBindToPopups(self):
        return bool(self.bindToPopups)

    cpdef py_void SetFunction(self, py_string name, object func):
        self.SetProperty(name, func)

    cpdef py_void SetObject(self, py_string name, object obj):
        if not hasattr(obj, "__class__"):
            raise Exception("JavascriptBindings.SetObject() failed: name=%s, "
                            "__class__ attribute missing, this is not an object" % name)
        cdef dict methods = {}
        cdef py_string key
        cdef object method
        cdef object predicate = inspect.ismethod
        if isinstance(obj, (PyBrowser, PyFrame)):
            predicate = inspect.isbuiltin
        for value in inspect.getmembers(obj, predicate=predicate):
            key = value[0]
            method = value[1]
            methods[key] = method
        self.objects[name] = methods

    cpdef object GetFunction(self, py_string name):
        if name in self.functions:
            return self.functions[name]

    cpdef dict GetFunctions(self):
        return self.functions

    cpdef dict GetObjects(self):
        return self.objects

    cpdef object GetObjectMethod(self, py_string objectName, py_string methodName):
        if objectName in self.objects:
            if methodName in self.objects[objectName]:
                return self.objects[objectName][methodName]

    cpdef object GetFunctionOrMethod(self, py_string name):
        # Name can be "someFunc" or "object.someMethod".
        cdef list words
        if "." in name:
            words = name.split(".")
            return self.GetObjectMethod(words[0], words[1])
        else:
            return self.GetFunction(name)

    cpdef py_void SetProperty(self, py_string name, object value):
        cdef object allowed = self.IsValueAllowedRecursively(value) # returns True or string.
        if allowed is not True:
            raise Exception("JavascriptBindings.SetProperty() failed: name=%s, "
                            "not allowed type: %s (this may be a type of a nested value)"
                            % (name, allowed))

        cdef object valueType = type(value)
        if IsFunctionOrMethod(valueType):
            self.functions[name] = value
        else:
            self.properties[name] = value

    cpdef py_void Rebind(self):
        # Rebind() is called for both first-time binding and rebinding.
        cdef PyBrowser pyBrowser
        cdef dict functions
        cdef dict properties
        cdef dict objects
        cdef dict methods
        for browserId, pyBrowser in g_pyBrowsers.iteritems():
            if pyBrowser.GetJavascriptBindings() != self:
                continue
            # Send to the Renderer process: functions, properties,
            # objects and its methods, bindToFrames.
            functions = {}
            for funcName in self.functions:
                functions[funcName] = None
            properties = self.properties
            objects = {}
            for objectName in self.objects:
                methods = {}
                for methodName in self.objects[objectName]:
                    methods[methodName] = None
                objects[objectName] = methods
            pyBrowser.SendProcessMessage(cef_types.PID_RENDERER,
                    0, "DoJavascriptBindings", [{
                            "functions": functions,
                            "properties": properties,
                            "objects": objects,
                            "bindToFrames": self.bindToFrames
                            }])

    cpdef dict GetProperties(self):
        return self.properties

    @staticmethod
    def IsValueAllowed(object value):
        return JavascriptBindings.IsValueAllowedRecursively(value) is True

    @staticmethod
    def IsValueAllowedRecursively(object value, py_bool recursion=False):
        # When making changes here modify also Frame.SetProperty() as it
        # checks for FunctionType, MethodType.

        cdef object valueType = type(value)
        cdef object valueType2
        cdef object key

        if valueType == list:
            for val in value:
                valueType2 = JavascriptBindings.IsValueAllowedRecursively(val, True)
                if valueType2 is not True:
                    return valueType2.__name__
            return True
        elif valueType == bool:
            return True
        elif valueType == float:
            return True
        elif valueType == int:
            return True
        elif valueType == type(None):
            return True
        elif IsFunctionOrMethod(valueType):
            if recursion:
                return valueType.__name__
            else:
                return True
        elif valueType == dict:
            for key in value:
                valueType2 = JavascriptBindings.IsValueAllowedRecursively(value[key], True)
                if valueType2 is not True:
                    return valueType2.__name__
            return True
        elif valueType == str or valueType == bytes:
            return True
        elif PY_MAJOR_VERSION < 3 and valueType == unicode:
            # The unicode type is not defined in Python 3.
            return True
        elif valueType == tuple:
            return True
        else:
            return valueType.__name__
