/*
 * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.cms.installer;

import com.arsdigita.categorization.Category;
import com.arsdigita.categorization.RootCategoryCollection;
import com.arsdigita.cms.ContentSection;
import com.arsdigita.cms.ContentType;
import com.arsdigita.cms.ContentTypeLifecycleDefinition;
import com.arsdigita.cms.ContentTypeWorkflowTemplate;
import com.arsdigita.cms.SecurityManager;
import com.arsdigita.cms.Template;
import com.arsdigita.cms.TemplateManagerFactory;
import com.arsdigita.cms.lifecycle.LifecycleDefinition;
import com.arsdigita.cms.lifecycle.PhaseDefinition;
import com.arsdigita.cms.util.GlobalizationUtil;
import com.arsdigita.cms.workflow.CMSTask;
import com.arsdigita.cms.workflow.UnfinishedTaskNotifier;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.initializer.InitializationException;
import com.arsdigita.kernel.ACSObjectInstantiator;
import com.arsdigita.kernel.EmailAddress;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.PartyCollection;
import com.arsdigita.kernel.Role;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.util.Assert;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.web.ApplicationSetup;
import com.arsdigita.workflow.simple.WorkflowTemplate;
import com.arsdigita.xml.XML;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.Timer;
import org.apache.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Sets up the content section.
 *
 * @author Jon Orris (jorris@redhat.com)
 * @version $Revision: #16 $ $DateTime: 2004/04/07 16:07:11 $
 */

public final class ContentSectionSetup {


    private final static String STYLESHEET = "/packages/content-section/xsl/cms.xsl";

    private static Logger s_log = Logger.getLogger(ContentSectionSetup.class);
    private HashMap m_tasks = new HashMap();
    private LifecycleDefinition m_lcd;
    private WorkflowTemplate m_wf;
    final ContentSection m_section;

    public ContentSectionSetup(ContentSection section) {
        Assert.exists(section, ContentSection.class);
        m_section = section;
    }

    public void run() {

        // 4) Mount content sections.

    }
    /**
     * setup content section app type
     */
    public static void setupContentSectionAppType() {
        ApplicationSetup setup = new ApplicationSetup(s_log);
        setup.setApplicationObjectType(ContentSection.BASE_DATA_OBJECT_TYPE);
        setup.setKey(ContentSection.PACKAGE_TYPE);
        setup.setTitle("CMS Content Section");
        setup.setDescription("A CMS Content Section");
        setup.setPortalApplication(false);
        //setup.setDispatcherClass(ContentItemDispatcher.class.getName());
        setup.setStylesheet(STYLESHEET);
        setup.setInstantiator(new ACSObjectInstantiator() {
            public DomainObject doNewInstance(DataObject dataObject) {
                return new ContentSection(dataObject);
            }
        });

        setup.run();

    }

    public void registerCategories(String filename) {

        if (filename == null) {
            s_log.info("not loading any categories");
            return;
        }

        s_log.info("loading categories from " + filename);

        XML.parseResource(filename, new CategoryHandler(m_section));
    }


    public void registerRoles(List roles) {

        Iterator i = roles.iterator();
        while (i.hasNext()) {
            List role = (List)i.next();

            String name = (String)role.get(0);
            String desc = (String)role.get(1);
            List privileges = (List)role.get(2);
            String task = (role.size() > 3 ? (String)role.get(3) : null);

            s_log.info("Creating role " + name);


            Role group = registerRole(
                    name,
                    desc,
                    privileges);

            if (task != null)
                m_tasks.put(task, group);
        }

    }

    Role registerRole(String name,
                              String desc,
                              List privileges) {
        Role role = m_section.getStaffGroup().createRole(name);
        role.setDescription(desc);
        role.save();

        Iterator i = privileges.iterator();
        while (i.hasNext()) {
            String priv = (String)i.next();
            s_log.info("Granting privilege cms_" + priv);

            role.grantPermission(m_section,
                    PrivilegeDescriptor.get("cms_" + priv));

            if (priv.equals(SecurityManager.CATEGORY_ADMIN) ||
                priv.equals(SecurityManager.CATEGORIZE_ITEMS)) {
                RootCategoryCollection coll = Category.getRootCategories(m_section);
                while (coll.next()) {
                    if (priv.equals(SecurityManager.CATEGORY_ADMIN)) {
                        role.grantPermission(coll.getCategory(),
                                             PrivilegeDescriptor.ADMIN);
                    } else {
                        role.grantPermission(coll.getCategory(),
                                             Category.MAP_DESCRIPTOR);
                    }
                }
            }
        }

        return role;
    }

    public void registerResolvers(String itemResolverClass, String templateResolverClass) {

        if (itemResolverClass != null && itemResolverClass.length()>0) {
            m_section.setItemResolverClass(itemResolverClass);
            s_log.info("Registering " + itemResolverClass + " as the item resolver class");
        } else {
            m_section.setItemResolverClass
                (ContentSection.getConfig().getDefaultItemResolverClass().getName());
            s_log.info("Registering " + itemResolverClass + " as the item resolver class");            
        }
        if (templateResolverClass != null && templateResolverClass.length()>0) {
            m_section.setTemplateResolverClass(templateResolverClass);
            s_log.info("Registering " + templateResolverClass + " as the template resolver class");
        } else {
            m_section.setTemplateResolverClass
                (ContentSection.getConfig().getDefaultTemplateResolverClass().getName());
            s_log.info("Registering " + templateResolverClass + " as the template resolver class");            
        }

        m_section.save();
    }

    public void registerWorkflowTemplates()
            throws InitializationException {

        // The 3-step production workflow.
        WorkflowTemplate wf = new WorkflowTemplate();
        wf.setLabel( (String) GlobalizationUtil.globalize("cms.installer.production_workflow").localize());
        wf.setDescription("A process that involves creating and approving content.");
        wf.save();

        CMSTask authoring = new CMSTask();
        authoring.setLabel((String) GlobalizationUtil.globalize("cms.installer.authoring").localize());
        authoring.setDescription("Create content.");
        authoring.save();


        Role author = (Role)m_tasks.get("Authoring");
        if (author != null)
            authoring.assignGroup(author.getGroup());

        authoring.setTaskType(CMSTask.AUTHOR);
        authoring.save();

        CMSTask approval = new CMSTask();
        approval.setLabel( (String) GlobalizationUtil.globalize("cms.installer.approval").localize());
        approval.setDescription("Approve content.");
        approval.save();
        approval.addDependency(authoring);
        approval.save();

        Role approver = (Role)m_tasks.get("Approval");
        if (approver != null)
            approval.assignGroup(approver.getGroup());

        approval.setTaskType(CMSTask.EDIT);
        approval.save();


        CMSTask deploy = new CMSTask();
        deploy.setLabel((String) GlobalizationUtil.globalize("cms.installer.deploy").localize());
        deploy.setDescription("Deploy content.");
        deploy.save();
        deploy.addDependency(approval);
        deploy.save();

        Role publisher = (Role)m_tasks.get("Publishing");
        if (publisher != null)
            deploy.assignGroup(publisher.getGroup());

        deploy.setTaskType(CMSTask.DEPLOY);
        deploy.save();

        wf.addTask(authoring);
        wf.addTask(approval);
        wf.addTask(deploy);
        wf.save();

        m_section.addWorkflowTemplate(wf);
        m_section.save();

        m_wf = wf;
    }

    public void registerViewers(Boolean pub) {
        Role viewers = m_section.getViewersGroup().createRole("Content Reader");
        viewers.setDescription("Can view published pages within this section");
        viewers.save();

        viewers.grantPermission(m_section,
                PrivilegeDescriptor.get("cms_read_item"));


        String email = Boolean.TRUE.equals(pub) ? "public@nullhost" : "registered@nullhost";

        Party viewer = retrieveParty(email);
        if (viewer == null)
            throw new InitializationException( (String) GlobalizationUtil.globalize("cms.installer.cannot_find_group_for_email").localize() + email);

        s_log.info("Adding " + email + " to viewers role");
        viewers.getGroup().addMemberOrSubgroup(viewer);
        viewers.save();
    }

    Party retrieveParty(String email) {
        PartyCollection parties = Party.retrieveAllParties();
        parties.filter(email);
        if (parties.next()) {
            Party party = parties.getParty();
            parties.close();
            return party;
        }
        return null;
    }

    public void registerAlerts() {
        Role alert = m_section.getStaffGroup().createRole("Alert Recipient");
        alert.setDescription("Receive alerts regarding expiration of pubished content");
        alert.save();
    }

    public void registerContentTypes(List types) {

        Iterator i = types.iterator();

        while (i.hasNext()) {
            Object obj = i.next();
            if (obj instanceof String) {
                registerContentType((String)obj);
            } else {
                List list = (List)obj;
                String name = (String)list.get(0);
                String file = (String)list.get(1);

                ContentType type = registerContentType(name);
                registerTemplate(type, file);
            }
        }
    }

    ContentType registerContentType(String name) {
        ContentType type = null;
        try {
            type = ContentType.findByAssociatedObjectType(name);
        } catch (DataObjectNotFoundException ex) {
            throw new UncheckedWrapperException( (String) GlobalizationUtil.globalize("cms.installer.cannot_find_content_type").localize() + name, ex);
        }

        s_log.info("Adding type " + name + " to " + m_section.getDisplayName());
        m_section.addContentType(type);

        s_log.info("Setting the default lifecycle for " + name + " to " + m_lcd.getLabel());
        ContentTypeLifecycleDefinition.
                updateLifecycleDefinition(m_section, type, m_lcd);
        m_lcd.save();

        s_log.info("Setting the default workflow template for " + name + " to " + m_wf.getLabel());
        ContentTypeWorkflowTemplate.updateWorkflowTemplate(m_section, type, m_wf);
        m_wf.save();

        return type;
    }

    void registerTemplate(ContentType type, String filename) {
        // Use the base of the file name (ie without path & extension)
        // as the template name
        int pos1 = filename.lastIndexOf("/");
        int pos2 = filename.lastIndexOf(".");

        if (pos2 == -1)
            pos2 = filename.length();

        String label = filename.substring(pos1+1,pos2);

        String typename = type.getClassName();
        int pos3 = typename.lastIndexOf(".");
        String name = typename.substring(pos3+1, typename.length()) + "-" + label;

        Template temp = new Template();
        temp.setContentSection(m_section);
        temp.setName(name);
        temp.setLabel(label);
        temp.setParent(m_section.getTemplatesFolder());

        final ClassLoader loader = Thread.currentThread
            ().getContextClassLoader();
        final InputStream stream = loader.getResourceAsStream
            (filename.substring(1));

        if (stream == null) {
            throw new IllegalStateException
                ((String) GlobalizationUtil.globalize
                 ("cms.installer.cannot_find_file").localize() + filename);
        }

        final BufferedReader input = new BufferedReader
            (new InputStreamReader(stream));

        StringBuffer body = new StringBuffer();
        String line;
        for (;;) {
            try {
                line = input.readLine();
            } catch (IOException ex) {
                throw new UncheckedWrapperException( (String) GlobalizationUtil.globalize("cms.installer.cannot_read_line_of_data").localize(),  ex);
            }
            if (line == null)
                break;

            body.append(line);
            body.append("\n");
        }

        temp.setText(body.toString());

        temp.save();

        TemplateManagerFactory.getInstance()
                .addTemplate(m_section, type, temp, "public");

        temp.publish(m_lcd, new Date());
        // Dan said to comment this out
        // temp.getLifecycle().start();
    }

    public void registerPublicationCycles()
            throws InitializationException {

        // The feature lifecycle.
        LifecycleDefinition lcd = new LifecycleDefinition();
        lcd.setLabel( (String) GlobalizationUtil.globalize("cms.installer.simple_publication").localize());
        lcd.setDescription("A one-phase lifecycle for items.");
        lcd.save();

        PhaseDefinition pd = lcd.addPhaseDefinition(
                "Live", "The first phase. It lasts forever.",
                new Integer(0), null, null);
        pd.save();

        lcd.save();

        m_lcd = lcd;

        m_section.addLifecycleDefinition(lcd);
        m_section.save();
    }

    /**
     *  SAX Handler for category lists.  Creates the categories as they are
     *  defined, with structure, in the xml document.
     */
    private class CategoryHandler extends DefaultHandler {
        private Stack m_cats = new Stack();
        private ContentSection m_section;

        public CategoryHandler(ContentSection section) {
            m_section = section;
        }

        public void startElement ( String uri, String local,
                                   String qName, Attributes attrs ) {
            if ("categories".equals(qName)) {
                String name = attrs.getValue("name");
                if (name == null) {
                    name = "Root";
                }
                String description = attrs.getValue("description");
                String context = attrs.getValue("context");
                
                Category root = Category.getRootForObject(m_section, 
                                                          context);
                if (root == null) {
                    root = new Category();
                }
                root.setName(name);
                root.setDescription(description);

                if (root.isNew()) {
                    Category.setRootForObject(m_section,
                                              root,
                                              context);
                }
                m_cats.push(root);
                PermissionService.setContext(root, m_section);
            } else if ( "category".equals(qName) ) {
                String name = attrs.getValue("name");
                String description = attrs.getValue("description");
                String url = attrs.getValue("url");

                // set the default description to the name of the category
                if ( description == null ) {
                    description = name;
                }

                s_log.debug("creating category '" + name + "'");
                Category cat = new Category(name, description, url);
                cat.save();

                Category parent = null;
                try {
                    parent = (Category)m_cats.peek();
                } catch ( EmptyStackException ex ) {
                    throw new UncheckedWrapperException("no root category", ex);
                }

                parent.addChild(cat);
                parent.save();
                cat.setDefaultParentCategory(parent);
                cat.save();

                m_cats.push(cat);
            }
        }

        public void endElement ( String uri, String local, String qName ) {
            if ( "category".equals(qName) ) {
                m_cats.pop();
            } else if ( "categories".equals(qName)) {
                m_cats.pop();
            }
        }
    }

    public void loadAlertPrefs(List tasks) {
        Iterator taskIter = null;
        if (tasks != null) {
            taskIter = tasks.iterator();
        }
        if (taskIter != null) {
            while (taskIter.hasNext()) {
                List oneTask = (List) taskIter.next();
                String taskName = (String) oneTask.get(0);
                List operationList = (List) oneTask.get(1);
                Iterator operationIter = null;
                if (operationList != null) {
                    operationIter = operationList.iterator();
                }
                if (operationIter != null) {
                    while (operationIter.hasNext()) {
                        String operation = (String) operationIter.next();
                        CMSTask.addAlert(m_section, taskName, operation);
                    }
                }
            }
        }
    }

    public Timer startNotifierTask(Boolean sendOverdue, Integer duration, Integer alertInterval, Integer max) {
        Timer unfinished = null;
        if (sendOverdue.booleanValue()) {
            if (duration == null || alertInterval == null || max == null) {
                s_log.info("Not sending overdue task alerts, required initialization parameters were not specified");
                return null;
            }
            // start the Timer as a daemon, so it doesn't keep the JVM from exiting
            unfinished = new Timer(true);
            UnfinishedTaskNotifier notifier = new UnfinishedTaskNotifier(m_section, duration.intValue(),
                    alertInterval.intValue(), max.intValue());
            // schedule the Task to start in 5 minutes, at 1 hour intervals
            unfinished.schedule(notifier, 5L * 60 * 1000, 60L * 60 * 1000);
            s_log.info("Sending overdue alerts for tasks greater than " + duration + " hours old");
        } else {
            s_log.info("Not sending overdue task alerts");
        }

        return unfinished;
    }

}
