/**
 * Copyright (C) 2010 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.xcmis.sp.inmemory.query;

import org.xcmis.search.SearchService;
import org.xcmis.search.content.ContentEntry;
import org.xcmis.search.content.IndexModificationException;
import org.xcmis.search.content.Property;
import org.xcmis.search.content.Property.BinaryValue;
import org.xcmis.search.content.Property.ContentValue;
import org.xcmis.search.content.Property.SimpleValue;
import org.xcmis.search.value.PropertyType;
import org.xcmis.spi.CmisConstants;
import org.xcmis.spi.ContentStream;
import org.xcmis.spi.DocumentData;
import org.xcmis.spi.FolderData;
import org.xcmis.spi.ObjectData;
import org.xcmis.spi.PolicyData;
import org.xcmis.spi.RelationshipData;
import org.xcmis.spi.utils.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * @author <a href="mailto:andrew00x@gmail.com">Andrey Parfonov</a>
 * @version $Id: IndexListener.java 1261 2010-06-09 09:52:06Z andrew00x $
 */
public class IndexListener
{

   private static final Logger LOG = Logger.getLogger(IndexListener.class);

   /**
    * Index storage.
    */
   private final SearchService searchService;

   private final ContentEntryAdapter contentEntryAdapter;

   public IndexListener(SearchService searchService)
   {
      if (searchService == null)
         throw new IllegalArgumentException("The searchService argument may not be null");
      this.searchService = searchService;
      this.contentEntryAdapter = new ContentEntryAdapter();
   }

   public void created(ObjectData object)
   {
      try
      {
         searchService.update(contentEntryAdapter.createEntry(object), null);
      }
      catch (IndexModificationException e)
      {
         if (LOG.isDebugEnabled())
         {
            LOG.debug(e.getLocalizedMessage());
         }
      }
      catch (IOException e)
      {
         if (LOG.isDebugEnabled())
         {
            LOG.debug(e.getLocalizedMessage());
         }
      }
   }

   public void removed(Set<String> removed)
   {
      try
      {
         searchService.update(Collections.EMPTY_LIST, removed);
      }
      catch (IndexModificationException e)
      {
         if (LOG.isDebugEnabled())
         {
            LOG.debug(e.getLocalizedMessage());
         }
      }
   }

   public void updated(ObjectData object)
   {
      try
      {
         searchService.update(contentEntryAdapter.createEntry(object), object.getObjectId());
      }
      catch (IndexModificationException e)
      {
         if (LOG.isDebugEnabled())
         {
            LOG.debug(e.getLocalizedMessage());
         }
      }
      catch (IOException e)
      {
         if (LOG.isDebugEnabled())
         {
            LOG.debug(e.getLocalizedMessage());
         }
      }
   }

   /**
    * Adapt changes produced by CMIS SPI to {@link ContentEntry} acceptable for.
    * {@link SearchService}
    */
   public static class ContentEntryAdapter
   {
      /**
       * Convert {@link ObjectData} to {@link ContentEntry}.
       *
       * @param objectData ObjectData
       * @return contentEntry ContentEntry
       * @throws IOException
       */
      public ContentEntry createEntry(ObjectData objectData) throws IOException
      {
         if (objectData != null)
         {
            switch (objectData.getBaseType())
            {
               case DOCUMENT :
                  return createFromDocument((DocumentData)objectData);
               case FOLDER :
                  return createFromFolder((FolderData)objectData);
               case POLICY :
                  return createFromPolicy((PolicyData)objectData);
               case RELATIONSHIP :
                  return createFromRelationship((RelationshipData)objectData);
               default :
                  throw new UnsupportedOperationException(objectData.getBaseType().toString()
                     + " is not supported for indexing");
            }
         }
         return null;
      }

      /**
       * @param objectData RelationshipData
       * @return ContentEntry ContentEntry;
       */
      private ContentEntry createFromRelationship(RelationshipData objectData)
      {
         MockContentEntry mockEntry = fillCommonInformation(objectData);
         //mark parent of root as parent
         mockEntry.parentIdentifiers.add("");
         return new ContentEntry(mockEntry.name, mockEntry.getTableNames(), mockEntry.identifier, mockEntry
            .getParentIdentifiers(), mockEntry.getProperties());
      }

      /**
       * @param objectData PolicyData
       * @return contentEntry ContentEntry
       */
      private ContentEntry createFromPolicy(PolicyData objectData)
      {
         MockContentEntry mockEntry = fillCommonInformation(objectData);
         //mark parent of root as parent
         mockEntry.parentIdentifiers.add("");
         return new ContentEntry(mockEntry.name, mockEntry.getTableNames(), mockEntry.identifier, mockEntry
            .getParentIdentifiers(), mockEntry.getProperties());
      }

      private MockContentEntry fillCommonInformation(ObjectData objectData)
      {
         MockContentEntry contentEntry = new MockContentEntry();
         contentEntry.tableNames.add(objectData.getTypeDefinition().getQueryName());
         contentEntry.identifier = objectData.getObjectId();
         contentEntry.name = objectData.getName();
         for (FolderData folder : objectData.getParents())
         {
            contentEntry.parentIdentifiers.add(folder.getObjectId());
         }

         for (org.xcmis.spi.model.Property<?> property : objectData.getProperties().values())
         {
            if (property.getValues().size() > 0)
            {
               contentEntry.properties.add(convertProperty(property));
            }
         }
         return contentEntry;
      }

      private <G> Property<G> convertProperty(org.xcmis.spi.model.Property<G> property)
      {
         Collection<ContentValue<G>> value = new ArrayList<ContentValue<G>>();
         for (G contentValue : property.getValues())
         {
            value.add(new SimpleValue<G>(contentValue));
         }

         return new Property<G>(CmisSchema.PROPERTY_TYPES_MAP.get(property.getType()), property.getQueryName(), value);
      }

      /**
       * Convert {@link FolderData} to {@link ContentEntry}.
       *
       * @param objectData FolderData
       * @return contentEntry ContentEntry
       */
      private ContentEntry createFromFolder(FolderData objectData)
      {
         MockContentEntry mockEntry = fillCommonInformation(objectData);
         return new ContentEntry(mockEntry.name, mockEntry.getTableNames(), mockEntry.identifier, mockEntry
            .getParentIdentifiers(), mockEntry.getProperties());
      }

      /**
       * Convert {@link DocumentData} to {@link ContentEntry}.
       *
       * @param objectData DocumentData
       * @return contentEntry ContentEntry.
       * @throws IOException
       */
      private ContentEntry createFromDocument(DocumentData objectData) throws IOException
      {
         MockContentEntry mockEntry = fillCommonInformation(objectData);
         ContentStream cs = objectData.getContentStream();
         if (cs != null)
         {
            List<ContentValue<InputStream>> vals = new ArrayList<ContentValue<InputStream>>(1);
            vals.add(new BinaryValue(cs.getStream(), cs.getMediaType().getBaseType(), cs.getMediaType().getParameter(
               CmisConstants.CHARSET), cs.length()));
            //TODO add constant for property name content
            mockEntry.properties.add(new Property<InputStream>(PropertyType.BINARY, "content", vals));
         }
         return new ContentEntry(mockEntry.name, mockEntry.getTableNames(), mockEntry.identifier, mockEntry
            .getParentIdentifiers(), mockEntry.getProperties());
      }
   }

   private static class MockContentEntry
   {
      /**
       * List of table names which identifies this content.
       */
      List<String> tableNames;

      /**
       * Name of the entry.
       */
      String name;

      /**
       * List of parent entry identifiers.
       */
      List<String> parentIdentifiers;

      /**
       * Entry identifier.
       */
      String identifier;

      /**
       * List of entry properties.
       */
      List<Property> properties;

      /**
       *
       */
      MockContentEntry()
      {
         tableNames = new ArrayList<String>();
         parentIdentifiers = new ArrayList<String>();
         properties = new ArrayList<Property>();
      }

      /**
       * @return the tableNames
       */
      public String[] getTableNames()
      {
         return tableNames.toArray(new String[tableNames.size()]);
      }

      /**
       * @return the parentIdentifiers
       */
      public String[] getParentIdentifiers()
      {
         return parentIdentifiers.toArray(new String[parentIdentifiers.size()]);
      }

      /**
       * @return the properties
       */
      public Property[] getProperties()
      {
         return properties.toArray(new Property[properties.size()]);
      }

   }
}
