#
#  Copyright (c) 2011 Helmut Merz helmutm@cy55.de
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

"""
Automatic setup of a loops site.

"""

import os
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.event import notify
from zope import component
from zope.cachedescriptors.property import Lazy
from zope.component import adapts
from zope.interface import implements, Interface
from zope.traversing.api import getName, traverse

from cybertools.typology.interfaces import IType
from loops.common import adapted
from loops.concept import ConceptManager, Concept
from loops.interfaces import ILoops, ITypeConcept, IPredicate
from loops.interfaces import IFile, IImage, ITextDocument, INote
#from loops.query import IQueryConcept
from loops.record import RecordManager
from loops.resource import ResourceManager, Resource
from loops.view import ViewManager, Node


class ISetupManager(Interface):
    """ An object that controls the setup of a loops site.
    """

    def setup():
        """ Set up a loops site: create all necessary objects and the
            relations between them.
        """


class SetupManager(object):

    adapts(ILoops)
    implements(ISetupManager)

    def __init__(self, context):
        self.context = context

    def setup(self):
        concepts, resources, views = self.setupManagers()
        self.setupCoreConcepts(concepts)
        appSetups = dict(component.getAdapters((self.context,), ISetupManager))
        for smName in appSetups:
            if smName: # skip core (unnamed), i.e. this, adapter
                appSetups[smName].setup()
        return concepts, resources, views # just for convenience when testing

    def setupManagers(self):
        loopsRoot = self.context
        concepts = self.addObject(loopsRoot, ConceptManager, 'concepts')
        resources = self.addObject(loopsRoot, ResourceManager, 'resources')
        views = self.addObject(loopsRoot, ViewManager, 'views')
        records = self.addObject(loopsRoot, RecordManager, 'records')
        return concepts, resources, views

    def setupCoreConcepts(self, conceptManager):
        typeConcept = self.addObject(conceptManager, Concept, 'type', title=u'Type')
        hasType = self.addObject(conceptManager, Concept, 'hasType', title=u'has Type')
        predicate = self.addObject(conceptManager, Concept, 'predicate',
                                    title=u'Predicate')
        standard = self.addObject(conceptManager, Concept, 'standard',
                                    title=u'subobject')
        domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain')
        #query = self.addObject(conceptManager, Concept, 'query', title=u'Query')
        file = self.addObject(conceptManager, Concept, 'file', title=u'File')
        textdocument = self.addObject(conceptManager, Concept,
                                      'textdocument', title=u'Text')
        note = self.addObject(conceptManager, Concept, 'note', title=u'Note')
        for c in (typeConcept, domain, note, file, textdocument, predicate):
            c.conceptType = typeConcept
            notify(ObjectModifiedEvent(c))
        ITypeConcept(typeConcept).typeInterface = ITypeConcept
        #ITypeConcept(query).typeInterface = IQueryConcept
        ITypeConcept(file).typeInterface = IFile
        ITypeConcept(textdocument).typeInterface = ITextDocument
        ITypeConcept(note).typeInterface = INote
        ITypeConcept(note).viewName = 'note.html'
        ITypeConcept(predicate).typeInterface = IPredicate
        hasType.conceptType = predicate
        standard.conceptType = predicate

    # standard properties and methods

    @Lazy
    def concepts(self):
        return self.context.getConceptManager()

    @Lazy
    def resources(self):
        return self.context.getResourceManager()

    @Lazy
    def views(self):
        return self.context.getViewManager()

    @Lazy
    def typeConcept(self):
        return self.concepts.getTypeConcept()

    @Lazy
    def predicateType(self):
        return self.concepts.getPredicateType()

    def addType(self, name, title, typeInterface=None, **kw):
        c = self.addConcept(name, title, self.typeConcept,
                            typeInterface=typeInterface, **kw)
        return c

    def addPredicate(self, name, title, **kw):
        c = self.addConcept(name, title, self.predicateType, **kw)
        return c

    def addConcept(self, name, title, conceptType, description=u'',
                   parentName=None, **kw):
        if name in self.concepts:
            self.log("Concept '%s' ('%s') already exists." % (name, title))
            c = self.concepts[name]
            if c.conceptType != conceptType:
                self.log("Wrong concept type for '%s': '%s' instead of '%s'." %
                         (name, getName(c.conceptType), getName(conceptType)))
        else:
            c = addAndConfigureObject(self.concepts, Concept, name, title=title,
                              description=description,
                              conceptType=conceptType, **kw)
            self.log("Concept '%s' ('%s') created." % (name, title))
        if parentName is not None:
            self.assignChild(parentName, name)
        return c

    def setConceptAttribute(self, concept, attr, value):
        setattr(adapted(concept), attr, value)
        self.log("Setting Attribute '%s' of '%s' to '%s'." %
                 (attr, getName(concept), repr(value)))

    def assignChild(self, conceptName, childName, predicate=None, **kw):
        predicate = self.getPredicate(predicate)
        concept = self.concepts[conceptName]
        child = self.concepts[childName]
        if child in concept.getChildren([predicate]):
            self.log("Concept '%s' is already a child of '%s' with predicate '%s'." %
                     (childName, conceptName, getName(predicate)))
        else:
            concept.assignChild(child, predicate, **kw)
            self.log("Concept '%s' assigned to '%s' with predicate '%s'." %
                     (childName, conceptName, getName(predicate)))

    def deassignChild(self, conceptName, childName, predicate=None):
        predicate = self.getPredicate(predicate)
        concept = self.concepts[conceptName]
        child = self.concepts[childName]
        concept.deassignChild(child, [predicate])
        self.log("Concept '%s' deassigned from '%s' for predicate '%s'." %
                    (childName, conceptName, getName(predicate)))

    def addResource(self, name, title, resourceType, description=u'', **kw):
        if name in self.resources:
            self.log("Resource '%s' ('%s') already exists." % (name, title))
            c = self.resources[name]
            if c.resourceType != resourceType:
                self.log("Wrong resource type for '%s': '%s' instead of '%s'." %
                         (name, getName(c.resourceType), getName(resourceType)))
        else:
            c = addAndConfigureObject(self.resources, Resource, name, title=title,
                              description=description,
                              resourceType=resourceType, **kw)
            self.log("Resource '%s' ('%s') created." % (name, title))
        return c

    def assignResource(self, conceptName, resourceName, predicate=None, **kw):
        predicate = self.getPredicate(predicate)
        concept = self.concepts[conceptName]
        resource = self.resources[resourceName]
        if resource in concept.getResources([predicate]):
            self.log("Resource '%s' is already assigned to '%s' with predicate '%s'.'" %
                     (resourceName, conceptName, getName(predicate)))
        else:
            concept.assignResource(resource, predicate, **kw)
            self.log("Resource '%s' assigned to '%s' with predicate '%s'." %
                     (resourceName, conceptName, getName(predicate)))

    def deassignResource(self, conceptName, resourceName, predicate=None):
        predicate = self.getPredicate(predicate)
        concept = self.concepts[conceptName]
        resource = self.resources[resourceName]
        concept.deassignResource(resource, [predicate])
        self.log("Resource '%s' deassigned from '%s' for predicate '%s'." %
                    (resourceName, conceptName, getName(predicate)))

    def addNode(self, name, title, container=None, nodeType='page',
                description=u'', body=u'', target=None, factory=Node, **kw):
        if container is None:
            container = self.views
            nodeType = 'menu'
        if name in container:
            self.log("Node '%s' ('%s') already exists in '%s'." %
                     (name, title, getName(container)))
            n = container[name]
            if n.nodeType != nodeType:
                self.log("Wrong node type for '%s': '%s' instead of '%s'." %
                         (name, n.nodeType, nodeType))
        else:
            n = addAndConfigureObject(container, factory, name, title=title,
                              description=description, body=body,
                              nodeType=nodeType, **kw)
            self.log("Node '%s' ('%s') created." % (name, title))
        if target is not None:
            targetObject = traverse(self, target, None)
            if targetObject is not None:
                if n.target == targetObject:
                    self.log("Target '%s' already assigned to node '%s'." %
                             (target, name))
                else:
                    n.target = targetObject
                    self.log("Target '%s' assigned to node '%s'." %
                             (target, name))
            else:
                self.log("Target '%s' for '%s' does not exist." %
                         (target, name))
        return n

    def getPredicate(self, predicate):
        if predicate is None:
            return self.concepts.getDefaultPredicate()
        if isinstance(predicate, basestring):
            return self.concepts[predicate]
        return predicate

    def log(self, message):
        if isinstance(message, unicode):
            message = message.encode('UTF-8')
        print >> self.logger, message

    def addObject(self, container, class_, name, **kw):
        return addObject(container, class_, name, **kw)

    def addAndConfigureObject(self, container, class_, name, **kw):
        return addAndConfigureObject(container, class_, name, **kw)


def addObject(container, class_, name, **kw):
    created = False
    if name in container:
        obj = container[name]
        #return obj
    else:
        obj = container[name] = class_()
        created = True
    for attr, value in kw.items():
        if attr == 'type':
            obj.setType(value)
        else:
            setattr(obj, attr, value)
    if created:
        notify(ObjectCreatedEvent(obj))
    notify(ObjectModifiedEvent(obj))
    return obj

def addAndConfigureObject(container, class_, name, **kw):
    basicAttributes = ('title', 'description', 'conceptType', 'resourceType',
                       'nodeType', 'body')
    basicKw = dict([(k, kw[k]) for k in kw if k in basicAttributes])
    obj = addObject(container, class_, name, **basicKw)
    adapted = obj
    if class_ in (Concept, Resource):
        ti = IType(obj).typeInterface
        if ti is not None:
            adapted = ti(obj)
    adapterAttributes = [k for k in kw if k not in basicAttributes]
    for attr in adapterAttributes:
        setattr(adapted, attr, kw[attr])
    notify(ObjectModifiedEvent(obj))
    return obj


def importData(root, importPath, importFileName):
    from loops.external.annotation import AnnotationsExtractor
    from loops.external.base import Loader
    from loops.external.pyfunc import PyReader
    component.provideAdapter(AnnotationsExtractor)
    dmpFile = open(os.path.join(importPath, importFileName))
    data = dmpFile.read()
    dmpFile.close()
    reader = PyReader()
    elements = reader.read(data)
    loader = Loader(root, os.path.join(importPath, 'resources'))
    loader.load(elements)


class SetupView(object):
    """ Allows to carry out setup actions manually.
    """

    def __init__(self, context, request):
        self.context = context
        self.request = request
        self.manager = ISetupManager(context)

    def setupLoopsSite(self):
        self.manager.setup()
        return 'Done'


