/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.dashboard.webui.component;

import org.exoplatform.application.registry.Application;
import org.exoplatform.application.registry.ApplicationRegistryService;
import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.config.StaleModelException;
import org.exoplatform.portal.config.model.Container;
import org.exoplatform.portal.config.model.Dashboard;
import org.exoplatform.portal.config.model.TransientApplicationState;
import org.exoplatform.portal.pom.spi.gadget.Gadget;
import org.exoplatform.portal.webui.application.UIGadget;
import org.exoplatform.portal.webui.application.UIPortlet;
import org.exoplatform.portal.webui.container.UIContainer;
import org.exoplatform.portal.webui.util.PortalDataMapper;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.web.application.RequestContext;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.application.portlet.PortletRequestContext;
import org.exoplatform.webui.config.InitParams;
import org.exoplatform.webui.config.Param;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.ParamConfig;
import org.exoplatform.webui.core.UIApplication;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.UIPopupMessages;
import org.exoplatform.webui.core.UIPortletApplication;
import org.exoplatform.webui.core.model.SelectItemOption;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IUnmarshallingContext;

import com.sun.mail.imap.protocol.UIDSet;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;

@ComponentConfig(template = "classpath:groovy/dashboard/webui/component/UIDashboardContainer.gtmpl", initParams = @ParamConfig(name = "ContainerConfigs", value = "classpath:conf/uiconf/dashboard/webui/container/ContainerConfig.groovy"))
public class UIDashboardContainer extends org.exoplatform.webui.core.UIContainer
{

   /** Specify max number of columns in dashboard container Default value is 4 */
   public static final int MAX_COLUMN = 4;

   /** Column Type of Container */
   public static final String COLUMN_CONTAINER = "column";

   /** Row Type of Container */
   public static final String ROW_CONTAINER = "row";

   /** Root Type of Container */
   public static final String ROOT_CONTAINER = "dashboard";

   /**
    *
    */
   private List<SelectItemOption<String>> containerOptions;

   /**
    * Specify windowId of <code>UIPortlet</code> that contains <code>UIDashboardPortlet</code> which this belong to
    *
    * @see UIPortlet
    * @see org.exoplatform.dashboard.webui.component.DashboardParent
    */
   private String windowId;

   /** Preconfig template */
   private String containerTemplate = null;

   public static final String COLINDEX = "colIndex";

   public static final String ROWINDEX = "rowIndex";

   /**
    * Constructs new UIDashboardContainer which belongs to a UIDashboardPortlet
    *
    * @param initParams initial parameters
    * @throws Exception if can't create UIDashboardContainer
    * @see org.exoplatform.dashboard.webui.component.DashboardParent
    * @see InitParams
    */
   public UIDashboardContainer(final InitParams initParams) throws Exception
   {
      if (initParams == null)
      {
         return;
      }
      WebuiRequestContext context = RequestContext.getCurrentInstance();
      windowId = ((PortletRequestContext)context).getRequest().getWindowID();

      Param param = initParams.getParam("ContainerConfigs");
      containerOptions = (List<SelectItemOption<String>>)param.getMapGroovyObject(context);
      if (containerOptions == null)
      {
         return;
      }

      //
      UIContainer uicontainer = addChild(UIContainer.class, null, null);

      //
      Container container = createContainer(COLUMN_CONTAINER, null);

      //
      PortalDataMapper.toUIContainer(uicontainer, container);

      //
      setColumns(3);
   }

   @Override
   public void processRender(WebuiRequestContext context) throws Exception
   {
      DataStorage service = getApplicationComponent(DataStorage.class);

      // Get current ui portlet containing this dashboard
      UIPortlet currentUIPortlet = UIPortlet.getCurrentUIPortlet();

      // Get dashboard for merging
      Container dashboard = null;
      if (currentUIPortlet.getStorageId() != null)
      {
         dashboard = service.loadDashboard(currentUIPortlet.getStorageId());
      }

      //
      if (dashboard != null)
      {
         UIContainer uiRoot = getChild(UIContainer.class);

         // Clear the children created by default as we get state from the storage
         uiRoot.getChildren().clear();

         // Assemble the dashboard
         PortalDataMapper.toUIContainer(uiRoot, dashboard);         
      }

      //
      super.processRender(context);
   }

   /**
    * Adds an UIGadget into UIDashboardContainer in specified position
    *
    * @param gadget UIGadget to add in UIDashboardContainer
    * @param col    index of column to add
    * @param row    index of row to add
    * @return <code>UIGadget</code> object which added in
    * @see UIGadget
    */
   public void addUIGadget(final UIGadget gadget, final int col, final int row)
   {
      UIContainer uiContainer = getColumn(col);
      List<UIComponent> children = uiContainer.getChildren();
      if (uiContainer == null || row < 0 || row > children.size())
      {
         return;
      }
      gadget.setParent(uiContainer);
      children.add(row, gadget);
   }

   /**
    * Gets an UIGadget at specified position
    *
    * @param col index of column
    * @param row index of row
    * @return <code>null</code> if specified position doesn't exist <code>UIGadget</code> otherwise
    * @see UIGadget
    */
   public UIGadget getUIGadget(final int col, final int row)
   {
      UIContainer uiContainer = getColumn(col);
      if (uiContainer == null || row < 0 || row >= uiContainer.getChildren().size())
      {
         return null;
      }
      return uiContainer.getChild(row);
   }

   /**
    * Gets an UIGadget which has applicationInstanceUniqueId_ according to specified Id
    *
    * @param gadgetId applicationInstanceUniqueId_ of UIGadget
    * @return <code>null</code> if UIGadget doesn't exist<br> <code>UIGadget<code> otherwise
    * @see UIGadget
    */
   public UIGadget getUIGadget(final String gadgetId)
   {
      List<UIContainer> columns = getColumns();
      for (int iCol = 0; iCol < getColumns().size(); iCol++)
      {
         for (int iRow = 0; iRow < columns.get(iCol).getChildren().size(); iRow++)
         {
            UIGadget gadget = (UIGadget)columns.get(iCol).getChild(iRow);
            if (gadgetId.equals(gadget.getId()))
            {
               return gadget;
            }
         }
      }
      return null;
   }

   /**
    * Removes an UIGadget belongs to this which has specified applicationInstanceUniqueId_
    *
    * @param gadgetId applicationInstanceUniqueId_ of UIGadget
    * @return <code>UIGadget</code> which deleted<br> <code>null</code> otherwise
    * @see UIGadget
    */
   public UIGadget removeUIGadget(final String gadgetId)
   {
      UIGadget gadget = getUIGadget(gadgetId);
      if (gadget != null)
      {
         UIContainer uiContainer = gadget.getParent();
         gadget.setParent(null);
         uiContainer.getChildren().remove(gadget);
      }
      return gadget;
   }

   /**
    * Removes an UIGadget at specified position
    *
    * @param col index of the column
    * @param row index of the row
    * @return <code>UIGadget</code> which deleted<br> <code>null</code> otherwise
    * @see UIGadget
    */
   public UIGadget removeUIGadget(final int col, final int row)
   {
      UIGadget gadget = getUIGadget(col, row);
      if (gadget != null)
      {
         removeUIGadget(gadget.getId());
      }
      return gadget;
   }

   /**
    * Moves an UIGadget which has specified applicationInstanceUniqueId_ to specified position
    *
    * @param gadgetId applicationInstanceUniqueId_ of UIGadget
    * @param col      index of destination column
    * @param row      index of destination row
    * @see UIGadget
    */
   public void moveUIGadget(final String gadgetId, final int col, final int row)
   {
      UIGadget gadget = removeUIGadget(gadgetId);
      if (gadget == null)
      {
         return;
      }
      addUIGadget(gadget, col, row);
   }

   /**
    * Gets an UIContainer representation of a column at specified index
    *
    * @param col index of column
    * @return <code>UIContainer</code> if the column exist<br> <code>null</code> otherwise
    * @see UIContainer
    */
   public UIContainer getColumn(final int col)
   {
      if (col < 0 || col > getColumns().size())
      {
         return null;
      }
      return getColumns().get(col);
   }

   /**
    * Tests if this UIDashboardContainer has UIGadget
    *
    * @return <code>false</code> if and only if this UIDashboardContainer has no UIGadget<br> <code>true</code>
    *         otherwise
    */
   public boolean hasUIGadget()
   {
      UIGadget gadget = findFirstComponentOfType(UIGadget.class);
      return (gadget != null);
   }

   /**
    * Sets total of columns of this UIDashboardContainer, total of columns is between 1 and <tt>MAX_COLUMN</tt>
    *
    * @param num total of columns
    * @return <code>null</code> if totals of columns is less than 1 or greater than <tt>MAX_COLUMN</tt> this
    *         <code>UIDashboardContainer</code> otherwise
    * @throws Exception if this UIDashboardContainer can not create new UIComponent
    */
   public UIDashboardContainer setColumns(final int num) throws Exception
   {
      if (num < 1 || num > MAX_COLUMN)
      {
         return null;
      }

      UIContainer uiRoot = findFirstComponentOfType(UIContainer.class);
      List<UIContainer> columns = getColumns();
      int colSize = columns.size();

      if (num < colSize)
      {
         for (int i = num; i < colSize; i++)
         {
            UIContainer tempCol = columns.get(i);
            List<UIComponent> components = new ArrayList<UIComponent>();
            for (UIComponent component : tempCol.getChildren())
            {
               columns.get(num - 1).addChild(component);
               components.add(component);
            }
            uiRoot.removeChildById(tempCol.getId());
            for (UIComponent component : components)
            {
               component.setParent(columns.get(num - 1));
            }
         }
      }
      else
      {
         if (num > colSize)
         {
            do
            {
               UIContainer uiContainer = createUIComponent(UIContainer.class, null, null);
               PortalDataMapper.toUIContainer(uiContainer, createContainer(ROW_CONTAINER, "UIColumn-" + colSize));
               uiRoot.addChild(uiContainer);
               colSize++;
            }
            while (num > colSize);
         }
      }
      return this;
   }

   /**
    * Returns a <tt>List</tt> of the UIContainers of this UIDashboardContainer which representation of list of columns.
    *
    * @return <code>List</code> of UIContainer
    * @see List
    * @see UIContainer
    */
   public List<UIContainer> getColumns()
   {
      List<UIContainer> list = new ArrayList<UIContainer>();
      UIContainer uiRoot = findFirstComponentOfType(UIContainer.class);
      uiRoot.findComponentOfType(list, UIContainer.class);
      if (list.size() > 0 && list.contains(uiRoot))
      {
         list.remove(uiRoot);
      }
      return list;
   }

   /**
    * Creates a Container that representation of row or column in this UIDashboardContainer
    *
    * @param type type of Container, that is <tt>ROW_CONTAINER</tt> or <tt>COLUMN_CONTAINER</tt>
    * @param id   id of Container
    * @return <code>Container</code> if {@link UIDashboardContainer} has specified type <br><code>null</code> otherwise
    * @throws Exception if <code>toContainer(String)</code> method throws an Exception
    * @see Container
    * @see UIDashboardContainer#toContainer(String)
    */
   public Container createContainer(final String type, final String id) throws Exception
   {
      for (SelectItemOption<String> item : containerOptions)
      {
         if (item.getLabel().equals(type))
         {
            Container container = toContainer(item.getValue());
            if (id != null)
            {
               container.setId(id);
            }
            return container;
         }
      }
      return null;
   }

   /**
    * Creates a Container from a xml template
    *
    * @param xml template of Container
    * @return <code>null</code> if template is not suitable for {@link Container}<br /> <code>Container</code>
    *         otherwise
    * @throws Exception if have problems in unmarshal process
    * @see IUnmarshallingContext#unmarshalDocument(java.io.InputStream, String)
    */
   private Container toContainer(final String xml) throws Exception
   {
      ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
      IBindingFactory bfact = BindingDirectory.getFactory(Container.class);
      IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
      return (Container)uctx.unmarshalDocument(is, null);
   }

   /**
    * Get windowId of <code>UIPortlet</code>
    *
    * @return a String represents windowId of portlet
    * @see UIPortlet
    */
   public String getWindowId()
   {
      return this.windowId;
   }

   /**
    *
    */
   public void setContainerTemplate(String template)
   {
      this.containerTemplate = template;
   }

   /**
    *
    */
   public String getContainerTemplate()
   {
      return this.containerTemplate;
   }

   /**
    * Saves all <tt>UIComponent</tt> of this <tt>UIDashboardContainer</tt> to database
    *
    * @throws Exception
    */
   public void save() throws Exception
   {
      UIContainer uiRoot = findFirstComponentOfType(UIContainer.class);

      if (!((UIDashboard)getParent()).canEdit())
      {
         return;
      }

      // Get current ui portlet containing this dashboard
      UIPortlet currentUIPortlet = UIPortlet.getCurrentUIPortlet();

      // We save only if we have an existing storage id
      // we would not have a storage id in the case where
      // the dashboard portlet is initially dropped in the
      // page 
      if (currentUIPortlet.getStorageId() != null)
      {
         // Build dashboard
         Dashboard dashboard = new Dashboard(currentUIPortlet.getStorageId());

         // Assemble the dashboard
         PortalDataMapper.toContainer(dashboard, uiRoot);

         // Get data storage
         DataStorage service = getApplicationComponent(DataStorage.class);

         // Save
         try
         {
            service.saveDashboard(dashboard);
         }
         catch (StaleModelException e)
         {
            getAncestorOfType(UIPortletApplication.class).addMessage(
               new ApplicationMessage("UIDashboard.msg.StaleData", null, ApplicationMessage.ERROR));            
         }
      }
   }

   public static class AddNewGadgetActionListener extends EventListener<UIDashboard>
   {
      @Override
      public final void execute(final Event<UIDashboard> event) throws Exception
      {
         WebuiRequestContext context = event.getRequestContext();
         UIDashboard uiDashboard = event.getSource();
         if (!uiDashboard.canEdit())
         {
            return;
         }
         int col = Integer.parseInt(context.getRequestParameter(COLINDEX));
         int row = Integer.parseInt(context.getRequestParameter(ROWINDEX));
         String objectId = context.getRequestParameter(UIComponent.OBJECTID);

         ApplicationRegistryService service = uiDashboard.getApplicationComponent(ApplicationRegistryService.class);
         Application application = service.getApplication(objectId);
         if (application == null)
         {
            UIApplication uiApplication = context.getUIApplication();
            uiApplication.addMessage(new ApplicationMessage("UIDashboard.msg.ApplicationNotExisted", null));
            return;
         }
         UIGadget uiGadget = event.getSource().createUIComponent(context, UIGadget.class, null, null);
         uiGadget.setState(new TransientApplicationState<Gadget>(application.getApplicationName()));
         UIDashboardContainer uiDashboardContainer = uiDashboard.getChild(UIDashboardContainer.class);
         uiDashboardContainer.addUIGadget(uiGadget, col, row);
         uiDashboardContainer.save();
         context.addUIComponentToUpdateByAjax(uiDashboardContainer);
      }
   }

   public static class MoveGadgetActionListener extends EventListener<UIDashboard>
   {
      @Override
      public final void execute(final Event<UIDashboard> event) throws Exception
      {
         WebuiRequestContext context = event.getRequestContext();
         UIDashboard uiDashboard = event.getSource();
         if (!uiDashboard.canEdit())
         {
            return;
         }
         UIDashboardContainer uiDashboardContainer = uiDashboard.getChild(UIDashboardContainer.class);
         int col = Integer.parseInt(context.getRequestParameter(COLINDEX));
         int row = Integer.parseInt(context.getRequestParameter(ROWINDEX));
         String objectId = context.getRequestParameter(OBJECTID);

         uiDashboardContainer.moveUIGadget(objectId, col, row);
         uiDashboardContainer.save();
         if (uiDashboard.getAncestorOfType(UIPortletApplication.class).getUIPopupMessages().hasMessage()) 
         {
            return;
         }
         Util.getPortalRequestContext().setResponseComplete(true);
      }
   }

   public static class DeleteGadgetActionListener extends EventListener<UIDashboard>
   {
      @Override
      public final void execute(final Event<UIDashboard> event) throws Exception
      {
         UIDashboard uiDashboard = event.getSource();
         if (!uiDashboard.canEdit())
         {
            return;
         }

         WebuiRequestContext context = event.getRequestContext();
         String objectId = context.getRequestParameter(OBJECTID);
         UIDashboardContainer uiDashboardContainer = uiDashboard.getChild(UIDashboardContainer.class);
         uiDashboardContainer.removeUIGadget(objectId);
         boolean isMaximized = false;
         if (uiDashboard.getMaximizedGadget() != null && 
                  uiDashboard.getMaximizedGadget().getId().equals(objectId))
         {
            uiDashboard.setMaximizedGadget(null);
            isMaximized = true;
         }
         uiDashboardContainer.save();
         UIPopupMessages uiPopupMessages = 
            uiDashboard.getAncestorOfType(UIPortletApplication.class).getUIPopupMessages();
         if (!isMaximized && !uiPopupMessages.hasMessage())
         {
            Util.getPortalRequestContext().setResponseComplete(true);
         }
      }
   }

}
