# -*- test-case-name: xmantissa.test.test_offering -*-

from zope.interface import implements

from twisted import plugin
from twisted.python.components import registerAdapter

from nevow import inevow, loaders, rend, athena
from nevow.athena import expose

from axiom import item, userbase, attributes, substore
from axiom.dependency import installOn

from xmantissa import ixmantissa, webnav, plugins

class OfferingAlreadyInstalled(Exception):
    """
    Tried to install an offering, but an offering by the same name was
    already installed.

    This may mean someone tried to install the same offering twice, or
    that two unrelated offerings picked the same name and therefore
    conflict!  Oops.
    """

class Benefactor(object):
    """
    I implement a method of installing and removing chunks of
    functionality from a user's store.
    """

class Offering(object):
    implements(plugin.IPlugin, ixmantissa.IOffering)

    def __init__(self,
                 name,
                 description,
                 siteRequirements,
                 appPowerups,
                 installablePowerups,
                 loginInterfaces,
                 themes,
                 version=None):
        self.name = name
        self.description = description
        self.siteRequirements = siteRequirements
        self.appPowerups = appPowerups
        self.installablePowerups = installablePowerups
        self.loginInterfaces = loginInterfaces
        self.themes = themes
        self.version = version

class InstalledOffering(item.Item):
    typeName = 'mantissa_installed_offering'
    schemaVersion = 1

    offeringName = attributes.text(doc="""
    The name of the Offering to which this corresponds.
    """, allowNone=False)

    application = attributes.reference(doc="""
    A reference to the Application SubStore for this offering.
    """)


def getOfferings():
    """
    Return the IOffering plugins available on this system.
    """
    return plugin.getPlugins(ixmantissa.IOffering, plugins)


def getInstalledOfferingNames(s):
    """
    Return a list of the names of the Offerings which are installed on the
    given store.

    @param s: Site Store on which offering installations are tracked.
    """
    return list(s.query(InstalledOffering).getColumn("offeringName"))


def getInstalledOfferings(s):
    """
    Return a mapping from the names of installed IOffering plugins to
    the plugins themselves.

    @param s: Site Store on which offering installations are tracked.
    """
    d = {}
    installed = getInstalledOfferingNames(s)
    for p in getOfferings():
        if p.name in installed:
            d[p.name] = p
    return d


def installOffering(s, offering, configuration):
    for off in s.query(InstalledOffering,
                       InstalledOffering.offeringName == offering.name):
        raise Exception("That Offering is already installed.")

    def siteSetup():
        for (requiredInterface, requiredPowerup) in offering.siteRequirements:
            if requiredInterface is not None:
                nn = requiredInterface(s, None)
                if nn is not None:
                    continue
            if requiredPowerup is None:
                raise NotImplementedError(
                    'Interface %r required by %r but not provided by %r' %
                    (requiredInterface, offering, s))
            s.findOrCreate(requiredPowerup, lambda p: installOn(p, s))

        ls = s.findOrCreate(userbase.LoginSystem)
        substoreItem = substore.SubStore.createNew(s, ('app', offering.name + '.axiom'))
        ls.addAccount(offering.name, None, None, internal=True,
                      avatars=substoreItem)
        ss = substoreItem.open()
        def appSetup():
            for pup in offering.appPowerups:
                installOn(pup(store=ss), ss)

        ss.transact(appSetup)
        # Woops, we need atomic cross-store transactions.
        io = InstalledOffering(store=s, offeringName=offering.name, application=substoreItem)
    s.transact(siteSetup)


class OfferingConfiguration(item.Item):
    """
    Provide administrative configuration tools for the L{IOffering}s available
    in this Mantissa server.
    """
    typeName = 'mantissa_offering_configuration_powerup'
    schemaVersion = 1

    installedOfferingCount = attributes.integer(default=0)
    installedOn = attributes.reference()
    powerupInterfaces = (ixmantissa.INavigableElement,)

    def installOffering(self, offering, configuration):
        s = self.store.parent
        self.installedOfferingCount += 1
        installOffering(s, offering, configuration)


    def getTabs(self):
        return [webnav.Tab('Admin', self.storeID, 0.3,
                           [webnav.Tab('Offerings', self.storeID, 1.0)],
                           authoritative=True)]



class UninstalledOfferingFragment(athena.LiveFragment):
    """
    Fragment representing a single Offering which has not been
    installed on the system.  It has a single remote method which will
    install it.
    """
    jsClass = u'Mantissa.Offering.UninstalledOffering'

    def __init__(self, original, offeringConfig, offeringPlugin, **kw):
        super(UninstalledOfferingFragment, self).__init__(original, **kw)
        self.offeringConfig = offeringConfig
        self.offeringPlugin = offeringPlugin

    def install(self, configuration):
        self.offeringConfig.installOffering(self.offeringPlugin, configuration)
    expose(install)


class OfferingConfigurationFragment(athena.LiveFragment):
    fragmentName = 'offering-configuration'
    live = 'athena'


    def __init__(self, *a, **kw):
        super(OfferingConfigurationFragment, self).__init__(*a, **kw)
        self.installedOfferings = getInstalledOfferingNames(self.original.store.parent)
        self.offeringPlugins = dict((p.name, p) for p in plugin.getPlugins(ixmantissa.IOffering, plugins))

    def head(self):
        return None

    def render_offerings(self, ctx, data):
        iq = inevow.IQ(ctx.tag)
        uninstalled = iq.patternGenerator('uninstalled')
        installed = iq.patternGenerator('installed')

        def offerings():
            for p in self.offeringPlugins.itervalues():
                data = {'name': p.name, 'description': p.description}
                if p.name not in self.installedOfferings:
                    f = UninstalledOfferingFragment(data, self.original, p, docFactory=loaders.stan(uninstalled()))
                    f.page = self.page
                else:
                    f = rend.Fragment(data, docFactory=loaders.stan(installed()))
                yield f

        return ctx.tag[offerings()]

registerAdapter(OfferingConfigurationFragment, OfferingConfiguration, ixmantissa.INavigableFragment)


syntax highlighted by Code2HTML, v. 0.9.1