#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
#  qooxdoo - the new era of web development
#
#  http://qooxdoo.org
#
#  Copyright:
#    2010-2010 1&1 Internet AG, Germany, http://www.1und1.de
#
#  License:
#    LGPL: http://www.gnu.org/licenses/lgpl.html
#    EPL: http://www.eclipse.org/org/documents/epl-v10.php
#    See the LICENSE file in the project's top-level directory for details.
#
#  Authors:
#    * Thomas Herchenroeder (thron7)
#
################################################################################

##
# Class -- Internal representation of a qooxdoo class; derives from Resource
##

import os, sys, re, types, copy
import time, math
from pprint import pprint

from misc                           import textutil
from ecmascript.frontend            import treeutil
from ecmascript.transform.optimizer import variantoptimizer
from generator.resource.Resource    import Resource
from generator                      import Context
from generator.code.clazz.MClassHints        import MClassHints
from generator.code.clazz.MClassI18N         import MClassI18N
from generator.code.clazz.MClassDependencies import MClassDependencies
from generator.code.clazz.MClassCode         import MClassCode
from generator.code.clazz.MClassResources    import MClassResources


class Class(Resource, MClassHints, MClassI18N, MClassDependencies, MClassCode, MClassResources):

    def __init__(self, name, path, library, context, container):
        #__slots__       = ('id', 'path', 'size', 'encoding', 'library', 'context', 'source', 'scopes', 'translations')
        global console, cache
        super(Class, self).__init__(path)
        self.id         = name  # qooxdoo name of class, classId
        self.library    = library     # Library()
        # TODO: we now have both a 'context' param, but also use generator.Context (needed in __setstate__)
        self.context    = context
        self._classesObj= container   # this is ugly, but curr. used to identify known names
        self.size       = -1
        self.encoding   = 'utf-8'
        self.source     = u''  # source text of this class
        #self.ast        = None # ecmascript.frontend.tree instance
        #self.type      = "" # PROPERTY
        self.scopes     = None # an ecmascript.frontend.Script instance
        self.translations = {} # map of translatable strings in this class
        self.resources  = set() # set of resource objects needed by the class
        self._assetRegex= {}  # [AssetHint], to hold regex's from #asset hints, for resource matching
        self.cacheId    = "class-%s" % self.path  # cache object for class-specific infos (outside tree, compile)
        
        console = context["console"]
        cache   = context["cache"]
        
        self.defaultIgnoredNamesDynamic = [lib["namespace"] for lib in self.context['jobconf'].get("library", [])]


    def __getstate__(self):
        d = self.__dict__.copy()
        # need to copy nested map, or i will modify original one
        d['context'] = d['context'].copy()
        del d['context']['cache']
        return d

    def __setstate__(self, d):
        if hasattr(Context, "cache"):
            d['context']['cache'] = Context.cache
        d['defaultIgnoredNamesDynamic'] = [lib["namespace"] for lib in d['context']['jobconf'].get("library", [])]
        self.__dict__ = d


    def _getType(self):
        if hasattr(self, "_type"):
            return self._type
        ast = self.tree()
        qxDefine = treeutil.findQxDefine(ast)
        classMap = treeutil.getClassMap(qxDefine)
        if 'type' in classMap:
            self._type = classMap['type'].get('value')
        elif 'extend' not in classMap:
            self._type = "static"  # this is qx.Class.define semantics!
        else:
            self._type = "normal"
        return self._type
        

    type = property(_getType)


    ##
    # classInfo = {
    #   'svariants' : ['qx.debug']    # supported variants
    #   'deps-<path>-<variants>' : ([<Dep>qx.Class#define], <timestamp>)  # class dependencies
    #   'messages-<variants>' : ["Hello %1"]  # message strings
    # }
    def _getClassCache(self):
        cache = self.context['cache']
        classInfo, modTime = cache.read(self.cacheId, self.path, memory=True)
        if classInfo:
            if self.writeCond():
                print "\nReading %s (keys: %s)" % (self.cacheId, 
                    ["%s:%s" % (i,self.foo(classInfo[i][1]) if len(classInfo[i])>1 else "-") for i in classInfo])
                for k in classInfo.keys():
                    if k.startswith("deps-"):
                        data = classInfo[k][0]['load']
                        print (sorted(data, key=str))
                        print "len:", len(data)
            return classInfo, modTime
        else:
            return {}, None
 
    def _writeClassCache(self, classInfo):
        cache = self.context['cache']
        if self.writeCond():
            import time
            print "\nWriting %s (keys: %s)" % (self.cacheId, 
                ["%s:%s" % (i,self.foo(classInfo[i][1]) if len(classInfo[i])>1 else "-") for i in classInfo])
            for k in classInfo.keys():
                if k.startswith("deps-"):
                    data = classInfo[k][0]['load']
                    print (sorted(data, key=str))
                    print "len:", len(data)
        cache.write(self.cacheId, classInfo, memory=True)


    def foo(s,t):
        d = time.strftime("%Y:%m:%d-%H:%M:%S::%%2.d", time.localtime(t))
        d = d % ((t-math.trunc(t))*100,)
        return d


    def writeCond(self):
        return False #self.id == "qx.core.Environment"


    @staticmethod
    def optimizeEnvironmentClass(envClass, compOptions):
        tree = envClass.tree(compOptions.variantset)
        # has to come before string optimization, or the "qx.debug" etc args are gone
        tree = variantoptimizer.processEnvironmentClass(tree, compOptions.allClassVariants)
        if compOptions.optimize:
            tree = envClass.optimize(tree, compOptions.optimize)
        return tree



##
# Duplication with clazz.ClassDependencies.DependencyItem
class DependencyItem(object):
    def __init__(self, name, attribute, requestor, line=-1, isLoadDep=False):
        self.name           = name       # "qx.Class" [dependency to (class)]
        assert isinstance(name, types.StringTypes)
        self.attribute      = attribute  # "methodA"   [dependency to (class.attribute)]
        self.requestor      = requestor  # "gui.Application" [the one depending on this item]
        self.line           = line       # 147        [source line in dependent's file]
        self.isLoadDep      = isLoadDep  # True       [load or run dependency]
        self.needsRecursion = False      # this is a load-time dep that draws in external deps recursively
        self.isCall         = False      # whether the reference is a function call
    def __repr__(self):
        return "<DepItem>:" + self.name + "#" + self.attribute
    def __str__(self):
        return self.name + "#" + self.attribute
    def __eq__(self, other):
        return self.name == other.name and self.attribute == other.attribute
    def __hash__(self):
        return hash(self.name + self.attribute)


##
# Throw this in cases of dependency problems
class DependencyError(ValueError): pass

 
##
# Auxiliary class for ClassDependencies() (although of more general appeal)
class ClassMap(object):
    def __init__(self):
        # after http://manual.qooxdoo.org/current/pages/core/class_quickref.html
        self.data = {
            'type'      : None,
            'extend'    : [],
            'implement' : [],
            'include'   : [],
            'construct' : [],
            'statics'   : {},  # { foo1 : [<dep1>,...], foo2 : [<dep2>,...] }
            'properties': {},
            'members'   : {},  # { foo1 : [<dep1>,...], foo2 : [<dep2>,...] }
            'settings'  : [],
            'variants'  : [],
            'events'    : [],
            'defer'     : [],
            'destruct'  : [],
        }
        return


##
# Captures the dependencies of a class (-file)
# - the main purpose of this is to have an accessible, shallow representation of
#   a class' dependencies, for caching and traversing
class ClassDependencies(object):
    def __init__(self):
        self.data = {
            'require' : [], # [qx.Class#define, #require(...), ... <other top-level code symbols>]
            'use'     : [], # [#use(...)]
            'optional': [], # [#optional(...)]
            'ignore'  : [], # [#ignore(...)]
            'classes' : {}, # {"classId" : ClassMap(), where the map values are lists of depsItems}
            }
        return

    ##
    # only iterates over the 'classes'
    def dependencyIterator(self):
        for classid, classMapObj in self.data['classes'].items():
            classMap = classMapObj.data
            for attrib in classMap:
                if isinstance(classMap[attrib], types.ListType):    # .defer
                    for dep in classMap[attrib]:
                        yield dep
                elif isinstance(classMap[attrib], types.DictType):  # .statics, .members, ...
                    for subattrib in classMap[attrib]:
                        for dep in classMap[attrib][subattrib]:     # e.g. methods
                            yield dep

    def getAttributeDeps(self, attrib):  # attrib="ignore", "qx.Class#define"
        res  = []
        data = self.data
        # top level
        if attrib.find('#')== -1:
            res = data[attrib]
        # class map
        else:
            classId, attribId = attrib.split('#', 1)
            data = data['classes'][classId].data
            if attribId in data:
                res = data[attribId]
            else:
                for submap in ('statics', 'members', 'properties'):
                    if attribId in data[submap]:
                        res = data[submap][attribId]
                        break
        return res
        

##
# Class to represent ["qx.util.*", "qx.core.Object"] et al.
# (like used in "include" and "exclude" config keys), to provide an
# encapsulated "match" method
class ClassMatchList(object):
    def __init__(self, matchlist):
        assert isinstance(matchlist, types.ListType)
        self.matchlist = matchlist   # ["a.b.c.*", "d.e.Foo"]
        elems = []
        for elem in matchlist:
            assert isinstance(elem, types.StringTypes)
            if elem != "":
                regexp = textutil.toRegExpS(elem)
                elems.append(regexp)
        if elems:
            self.__regexp = re.compile("|".join(elems))
        else:
            self.__regexp = re.compile(r".\A")  # match none

    def isEmpty(self):
        return len(self.matchlist) == 0

    def match(self, classId):
        return self.__regexp.search(classId)


##
# Class to collect various options which influence the compilation process
# (optimizations, format, variants, ...)
class CompileOptions(object):
    def __init__(self, optimize=[], variants={}, _format=False, source_with_comments=False):
        self.optimize   = optimize
        self.variantset = variants
        self.format     = _format
        self.source_with_comments = source_with_comments
        self.privateMap = {} # {"<classId>:<private>":"<repl>"}

