/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *     "Apache Jetspeed" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache" or
 *    "Apache Jetspeed", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.jetspeed.modules.actions.controllers;

//Jetspeed Stuff
import org.apache.jetspeed.om.profile.Profile;
import org.apache.jetspeed.om.registry.RegistryEntry;
import org.apache.jetspeed.om.profile.Portlets;
import org.apache.jetspeed.om.profile.Entry;
import org.apache.jetspeed.om.profile.Layout;
import org.apache.jetspeed.om.profile.psml.PsmlLayout;
import org.apache.jetspeed.portal.PortletSet;
import org.apache.jetspeed.portal.PortletController;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.Registry;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.util.AutoProfile;

// Turbine stuff
//import org.apache.turbine.util.DynamicURI;
import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.ActionLoader;

// Velocity Stuff
import org.apache.velocity.context.Context;

// Java stuff
import java.util.Vector;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.Comparator;

/**
 * This action builds a context suitable for controllers handlings simple
 * sorted lists of portlets
 *
 * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>
 * @author <a href="mailto:paulsp@apache.org">Paul Spencer</a>
 */
public class RowColumnControllerAction extends VelocityControllerAction
{

    /**
     * Static initialization of the logger for this class
     */
    private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(RowColumnControllerAction.class.getName());
    
    /**
     * Subclasses must override this method to provide default behavior
     * for the portlet action
     */
    protected void buildNormalContext( PortletController controller,
                                       Context context,
                                       RunData rundata )
    {
        //retrieve the size for each of the element
        String sizes = controller.getConfig().getInitParameter("sizes");
        context.put("sizes", getCellSizes(sizes));
    }

    /** Parses the size config info and returns a list of
     *  size values for the current set
     *
     *  @param sizeList java.lang.String a comma separated string a values
     *  @return a List of values
     */
    public static List getCellSizes(String sizelist)
    {
        List list = new Vector();

        if (sizelist!=null)
        {
            StringTokenizer st = new StringTokenizer(sizelist,",");
            while (st.hasMoreTokens())
            {
                list.add(st.nextToken());
            }
        }

        return list;
    }

    /**
     * Adds a "pane" portlet object in the context which represents the
     * currently selected pane
     */
    protected void buildCustomizeContext( PortletController controller,
                                       Context context,
                                       RunData rundata )
    {
        super.buildCustomizeContext(controller, context, rundata);

        JetspeedRunData jdata = (JetspeedRunData)rundata;
        PortletSet set = (PortletSet)jdata.getCustomized();

        Portlets portlets = jdata.getCustomizedProfile()
                                 .getDocument()
                                 .getPortletsById(set.getID());

        List plist = new ArrayList();
        List work = new ArrayList();
        List filler = Collections.nCopies(portlets.getPortletsCount()+portlets.getEntryCount(),null);
        plist.addAll(filler);

        for (int i=0; i < portlets.getPortletsCount(); i++)
        {
            Portlets p = portlets.getPortlets(i);
            if (logger.isDebugEnabled())
            {
                logger.debug("RowColumnControllerAction: processing portlet: " + p.getTitle());
            }
            Layout layout = p.getLayout();
            if (layout == null)
            {
                // Pane should always have a layout with correct position
                if (logger.isDebugEnabled())
                {
                    logger.debug("RowColumnControllerAction: no layout, creating a new one");
                }
                layout = new PsmlLayout();
                layout.setPosition(i);
                p.setLayout(layout);
            }
            if (layout!=null)
            {
                try
                {
                    int pos = (int)layout.getPosition();
                    if (logger.isDebugEnabled())
                    {
                        logger.debug("RowColumnControllerAction: layout has position: " + pos);
                    }
                    if (pos >= 0 && pos < plist.size())
                    {
                        plist.set(pos,p);
                    }
                    else
                    {
                        work.add(p);
                    }
                }
                catch (Exception e)
                {
                    logger.error("Layout error", e);
                    layout.setPosition(-1);
                    work.add(p);
                }
            }
            else
            {
                work.add(p);
            }
        }

        for (int i=0; i < portlets.getEntryCount(); i++)
        {
            Entry p = portlets.getEntry(i);
            Layout layout = p.getLayout();
            if (layout!=null)
            {
                try
                {
                    int pos = (int)layout.getPosition();
                    if (pos>=0)
                    {
                        plist.set(pos,p);
                    }
                    else
                    {
                        work.add(p);
                    }
                }
                catch (Exception e)
                {
                    layout.setPosition(-1);
                    work.add(p);
                }
            }
            else
            {
                work.add(p);
            }
        }

        Iterator i = work.iterator();
        for(int idx=0;idx < plist.size(); idx++)
        {
            if (plist.get(idx)==null)
            {
                if (i.hasNext())
                {
                    plist.set(idx,i.next());
                }
                else
                {
                    plist.remove(idx);
                }
            }
        }

        Map titles = new HashMap();
        i = plist.iterator();
        while(i.hasNext())
        {
            Object obj = i.next();

            if (obj instanceof Portlets)
            {
                Portlets entry = (Portlets)obj;
                if ((entry.getMetaInfo()!=null)&&(entry.getMetaInfo().getTitle()!=null))
                {
                    titles.put(entry.getId(),entry.getMetaInfo().getTitle());
                }
            }
            else
            {
                Entry entry = (Entry)obj;
                if ((entry.getMetaInfo()!=null)&&(entry.getMetaInfo().getTitle()!=null))
                {
				   titles.put(entry.getId(), entry.getMetaInfo().getTitle());
                }
                else
                {
                    RegistryEntry pentry = Registry.getEntry(Registry.PORTLET,entry.getParent());

                    if (pentry!=null)
                    {
						titles.put(entry.getId(), pentry.getTitle());
                    }
                }
            }
        }


        context.put("portlets",plist);
        context.put("titles",titles);

        /**
         * Make a list of all used portlets available thru the 'runs' reference
         * --------------------------------------------------------------------------
         * last modified: 10/31/01
         * Andreas Kempf, Siemens ICM S CP PE, Munich
         * mailto: A.Kempf@web.de
         */
        context.put("runs", AutoProfile.getPortletList(rundata));
        // --------------------------------------------------------------------------
    }

    /**
     * Cancel the current customizations.  If this was the last customization
     * on the stack, then return the user to the home page.
     */
    public void doCancel(RunData data, Context context)
    {
         ((JetspeedRunData)data).setCustomized(null);

        if (((JetspeedRunData)data).getCustomized() == null)
        {
            try
            {
                ActionLoader.getInstance().exec( data, "controls.EndCustomize" );
            }
            catch (Exception e)
            {
                logger.error("Unable to load action controls.EndCustomize ",e);
            }
        }
    }

    public void doSave(RunData data, Context context)
    {
        doApply(data, context);
    }

    public void doApply(RunData data, Context context)
    {
        // move one level back in customization
        ((JetspeedRunData) data).setCustomized(null);

        // if we are all done customization
        if (((JetspeedRunData) data).getCustomized() == null)
        {
            // save the edit profile and make it current
            try
            {
                ((JetspeedRunData) data).getCustomizedProfile().store();
            }
            catch (Exception e)
            {
                logger.error("Unable to save profile ",e);
            }

            try
            {
                ActionLoader.getInstance().exec( data, "controls.EndCustomize" );
            }
            catch (Exception e)
            {
                logger.error("Unable to load action controls.EndCustomize ",e);
            }
        }
    }

    /** Remove a pane from the current set
     *  This method expects the following parameters
     * - paneid: the id a the pane to modify within the current profile
     * - position: the position of the component to delete
     */
    public void doDelete(RunData data, Context context) throws Exception
    {
      JetspeedRunData jdata = (JetspeedRunData)data;
      PortletSet customizedSet = (PortletSet)jdata.getCustomized();
      int position = data.getParameters().getInt("position",-1);
      Profile profile = jdata.getCustomizedProfile();

      // ADDED for WML
      //boolean isWML = (profile.getMediaType().equalsIgnoreCase("wml"));


        if (customizedSet == null) return;

        if (position > -1)
        {
            Portlets set = profile.getDocument()
                                  .getPortletsById(customizedSet.getID());
            if (set != null)
            {
                // first try explicit portlets position
                for(int i=0; i < set.getPortletsCount(); i++)
                {
                    Portlets p = set.getPortlets(i);
                    Layout layout = p.getLayout();
//
//                    if (layout == null)
//                    {
//                      Layout nl = new Layout ();
//                      nl.setPosition (String.valueOf(i));
//
//                    }
                    if ((layout!=null) && (layout.getPosition() != -1))
                    {

                        int lpos = (int)layout.getPosition();
                        if (lpos==position)
                        {
                            set.removePortlets(i);

                            updateLayoutPositions(set);

                            // MODIFIED: Save changes for wml profiles
                            //if (isWML)
                            //  doSave(data, null);

                            return;
                        }
                    }
                }

                // try explicit entry position
                for(int i=0; i < set.getEntryCount(); i++)
                {
                    Entry p = set.getEntry(i);
                    Layout layout = p.getLayout();

                    if ((layout!=null) && (layout.getPosition() != -1))
                    {
                        int lpos = (int)layout.getPosition();

                        if (lpos==position)
                        {
                            set.removeEntry(i);

                            // MODIFIED: Save changes for wml profiles
                            //if (isWML)
                            //  doSave(data, null);
                            return;
                        }
                    }
                }

                //else use implicit position
                if (position < set.getPortletsCount())
                {
                    set.removePortlets(position);

                    // MODIFIED: Save changes for wml profiles
                    //if (isWML)
                    //  doSave(data, null);
                    return;
                }

                if (position < set.getEntryCount())
                {
                    set.removeEntry(position);

                    // MODIFIED: Save changes for wml profiles
                    //if (isWML)
                    //  doSave(data, null);
                    return;
                }
            }
        }


    }

    /**
     * Updates the layout position based on physical order within the resorted portlet list. Assures that 
     * layout position is always consecutive and within bounds.
     * 
     * @param set
     */
    private void updateLayoutPositions(Portlets set)
    {
        // Load the panes into a list
        List list = new ArrayList();
        for(int i = 0; i < set.getPortletsCount(); i++)
        {
            Portlets pane = set.getPortlets(i);
            list.add(pane);
        }

        // Sort list using the current layout position
        Collections.sort(list, 
                         new Comparator()
                         {
                             public int compare(Object pane1, Object pane2)
                             {
                                 Long pos1 = new Long(((Portlets) pane1).getLayout().getPosition());
                                 Long pos2 = new Long(((Portlets) pane2).getLayout().getPosition());
                                 return pos1.compareTo(pos2);
                             }
                         });

        // Update the layout position based on the physical order within the sorted list
        int position = 0;
        for (Iterator iter = list.iterator(); iter.hasNext();)
        {
            Portlets pane = (Portlets) iter.next();
            Layout layout = pane.getLayout();
            layout.setPosition(position++);
        }
    }

    /** Move a component up within the pane
     *  This method expects the following parameters
     * - paneid: the id a the pane to modify within the current profile
     * - position: move the component which occupies this position
     */
    public void doUp(RunData data, Context context) throws Exception
    {
        doMove(data,context,true);
    }

    /** Move a component down within the pane
     *  This method expects the following parameters
     * - paneid: the id a the pane to modify within the current profile
     * - position: move the component which occupies this position
     */
    public void doDown(RunData data, Context context) throws Exception
    {
        doMove(data,context,false);
    }

    /** Move a component within the pane
     *  This method expects the following parameters
     * - paneid: the id a the pane to modify within the current profile
     * - position: move the component which occupies this position
     * The moveUp boolean determines the direction of the move
     */
    public void doMove(RunData data, Context context, boolean moveUp) throws Exception
    {
        JetspeedRunData jdata = (JetspeedRunData)data;
        PortletSet customizedSet = (PortletSet)jdata.getCustomized();
        int position = data.getParameters().getInt("position",-1);
        Profile profile = jdata.getCustomizedProfile();

        // ADDED for WML
        //boolean isWML = (profile.getMediaType().equalsIgnoreCase("wml"));


        if (customizedSet == null) return;

        if (position > -1)
        {
            int target = -1;

            Portlets set = profile.getDocument()
                                  .getPortletsById(customizedSet.getID());
            Layout targetLayout = null;
            Layout baseLayout = null;

            if (set != null)
            {
                // check if we can possibly move as requested and calculate
                // target position
                if ( moveUp )
                {
                    if ( (position >= set.getPortletsCount())
                         && (position >= set.getEntryCount()) ) return;
                    target = position + 1;
                }
                else
                {
                    if (position ==0) return;
                    target = position - 1;
                }

                // first find objects at explicit portlets position
                for(int i=0; i < set.getPortletsCount(); i++)
                {
                    if ((targetLayout!=null) && (baseLayout!=null)) break;

                    Portlets p = set.getPortlets(i);
                    Layout layout = p.getLayout();
                    if ((layout!=null)&&(layout.getPosition()!=-1))
                    {
                        int lpos = (int)layout.getPosition();
                        if ((baseLayout == null) && (lpos==position))
                        {
                            baseLayout = layout;
                        }

                        if ((targetLayout == null) && (lpos==target))
                        {
                            targetLayout = layout;
                        }
                    }
                }

                // try explicit entry position
                for(int i=0; i < set.getEntryCount(); i++)
                {
                    if ((targetLayout!=null) && (baseLayout!=null)) break;

                    Entry p = set.getEntry(i);
                    Layout layout = p.getLayout();
                    if ((layout!=null)&&(layout.getPosition()!=-1))
                    {
                        int lpos = (int)layout.getPosition();
                        if ((baseLayout == null) && (lpos==position))
                        {
                            baseLayout = layout;
                        }

                        if ((targetLayout == null) && (lpos==target))
                        {
                            targetLayout = layout;
                        }
                    }
                }

                //else use implicit position
                if (baseLayout == null)
                {
                    if (position < set.getPortletsCount())
                    {
                        Portlets p = set.getPortlets(position);
                        if (p.getLayout()==null)
                        {
                            p.setLayout(new PsmlLayout());
                        }
                        baseLayout=p.getLayout();
                    }

                    if (position < set.getEntryCount())
                    {
                        Entry p = set.getEntry(position);
                        if (p.getLayout()==null)
                        {
                            p.setLayout(new PsmlLayout());
                        }
                        baseLayout=p.getLayout();
                    }
                }

                if (targetLayout == null)
                {
                    if (target < set.getPortletsCount())
                    {
                        Portlets p = set.getPortlets(target);
                        if (p.getLayout()==null)
                        {
                            p.setLayout(new PsmlLayout());
                        }
                        targetLayout=p.getLayout();
                    }

                    if (target < set.getEntryCount())
                    {
                        Entry p = set.getEntry(target);
                        if (p.getLayout()==null)
                        {
                            p.setLayout(new PsmlLayout());
                        }
                        targetLayout=p.getLayout();
                    }
                }

                //we should now have found both baseLayout and targetLayout, swap
                //their positions using explicit positioning

                if ((baseLayout == null) || (targetLayout == null)) return;

                baseLayout.setPosition(target);
                targetLayout.setPosition(position);
            }
        }


    // MODIFIED: Save changes for wml profiles
    //if (isWML)
    //  doSave(data, null);
    }
}
