/**
 *
 * Magnolia SimpleMedia Module (http://www.openmindlab.com/lab/products/media.html)
 * Copyright (C)2008 - 2010, Openmind S.r.l. http://www.openmindonline.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sourceforge.openutils.mgnlmedia.media.configuration;

import info.magnolia.cms.beans.config.ContentRepository;
import info.magnolia.cms.beans.config.ObservedManager;
import info.magnolia.cms.beans.config.URI2RepositoryManager;
import info.magnolia.cms.beans.config.URI2RepositoryMapping;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.ItemType;
import info.magnolia.cms.core.search.Query;
import info.magnolia.cms.core.search.QueryManager;
import info.magnolia.cms.core.search.QueryResult;
import info.magnolia.cms.util.ContentUtil;
import info.magnolia.cms.util.FactoryUtil;
import info.magnolia.cms.util.NodeDataUtil;
import info.magnolia.content2bean.Content2BeanUtil;
import info.magnolia.context.MgnlContext;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.jcr.RepositoryException;
import javax.jcr.query.InvalidQueryException;

import net.sourceforge.openutils.mgnlmedia.media.lifecycle.MediaModule;
import net.sourceforge.openutils.mgnlmedia.media.pages.MediaFolderViewPage;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.util.ISO9075;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * ObservedManager that keeps that media types configuration.<br/>
 * Each media type is defined under the mediatypes contentnode in /modules/media.<br/>
 * I.e.<br/>
 * /modules/media/mediatypes/pdf<br/>
 * <ul>
 * <li>label = pdf file</li>
 * <li>handler = info.acme.PdfTypeHandler</li>
 * <li>menuIcon = .resources/pdf/icons/pdf16.gif</li>
 * <li>extensions = pdf</li>
 * </ul>
 * @author molaschi
 */
public class MediaConfigurationManager extends ObservedManager
{

    private static final String MGNL_MEDIA_TYPE = "mgnl:media";

    private static final String MGNL_RESOLUTION_TYPE = "mgnl:resolutions";

    /**
     * Folder type
     */
    public static final ItemType FOLDER = ItemType.CONTENT;

    /**
     * Media type
     */
    public static final ItemType MEDIA = new ItemType(MGNL_MEDIA_TYPE);

    /**
     * Resolutions node type
     */
    public static final ItemType RESOLUTIONS = new ItemType(MGNL_RESOLUTION_TYPE);

    private Logger log = LoggerFactory.getLogger(MediaConfigurationManager.class);

    private Map<String, MediaTypeConfiguration> types = new LinkedHashMap<String, MediaTypeConfiguration>();

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onClear()
    {
        types.clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    protected void onRegister(Content node)
    {
        for (Iterator iter = ContentUtil.getAllChildren(node).iterator(); iter.hasNext();)
        {
            Content typeNode = (Content) iter.next();

            if (!NodeDataUtil.getBoolean(typeNode, "enabled", true))
            {
                continue;
            }

            try
            {
                MediaTypeConfiguration conf = (MediaTypeConfiguration) Content2BeanUtil.toBean(
                    typeNode,
                    true,
                    MediaTypeConfiguration.class);

                if (conf.getHandler() != null)
                {
                    conf.getHandler().init(typeNode);
                }
                else
                {
                    log.error("Missing handler for media type {}", typeNode.getName());
                    continue;
                }

                types.put(typeNode.getName(), conf);
            }
            catch (Throwable e)
            {
                log.error("Error getting media type configuration for {}", typeNode.getHandle(), e);
            }
        }
    }

    /**
     * Get singleton instance
     * @return singleton instance
     */
    public static MediaConfigurationManager getInstance()
    {
        return (MediaConfigurationManager) FactoryUtil.getSingleton(MediaConfigurationManager.class);
    }

    /**
     * Get the media type from a file extension
     * @param extension file extension
     * @return media type
     */
    public static MediaTypeConfiguration getMediaHandlerFromExtension(String extension)
    {
        for (Map.Entry<String, MediaTypeConfiguration> entry : MediaConfigurationManager
            .getInstance()
            .getTypes()
            .entrySet())
        {
            if (entry.getValue().getExtensionsList().contains(extension.toLowerCase()))
            {
                return entry.getValue();
            }
        }
        return null;
    }

    /**
     * Get all media type map
     * @return media type map
     */
    public Map<String, MediaTypeConfiguration> getTypes()
    {
        return types;
    }

    /**
     * Get all media nodes in a folder
     * @param folder folder
     * @return all media nodes
     */
    public Collection<Content> getMediaNodes(Content folder)
    {
        return getMediaNodes(folder, null, MediaFolderViewPage.SORT_BY_MODIFICATIONDATE);
    }

    /**
     * Get the list of web pages where a media is used
     * @param uuid uuid of media
     * @return list of web pages where a media is used
     * @throws InvalidQueryException invalid query
     * @throws RepositoryException repository exception
     */
    @SuppressWarnings("unchecked")
    public List<String> getUsedInWebPages(String uuid) throws InvalidQueryException, RepositoryException
    {
        List<String> handles = new ArrayList<String>();
        QueryManager qm = MgnlContext.getQueryManager(ContentRepository.WEBSITE);

        Query q = qm.createQuery("select * from nt:base where contains(., '" + uuid + "')", Query.SQL);
        QueryResult qr = q.execute();
        Collection<Content> nodes = qr.getContent(ItemType.CONTENT.getSystemName());
        if (nodes != null)
        {
            for (Content c : nodes)
            {
                if (!handles.contains(c.getHandle()))
                {
                    handles.add(c.getHandle());
                }

            }
        }
        return handles;
    }

    /**
     * Get all media nodes of given type in a folder
     * @param folder folder
     * @param type media
     * @param sorting sorting
     * @return all media nodes of passed type
     */
    @SuppressWarnings("unchecked")
    public Collection<Content> getMediaNodes(final Content folder, final String type, final String sorting)
    {
        List<Content> medias = (List<Content>) folder.getChildren(new Content.ContentFilter()
        {

            /**
             * {@inheritDoc}
             */
            public boolean accept(Content content)
            {
                try
                {
                    return content.getItemType().equals(MEDIA) && NodeDataUtil.getString(content, "type").equals(type);
                }
                catch (RepositoryException e)
                {
                    log.error("Error getting item type on node {} module media", folder.getHandle(), e);
                }
                return false;
            }

        });
        final MediaConfigurationManager mcm = this;
        Collections.sort(medias, new Comparator<Content>()
        {

            private Map<String, Integer> mapNpages = new HashMap<String, Integer>();

            /**
             * {@inheritDoc}
             */
            public int compare(Content o1, Content o2)
            {
                if (sorting.equals(MediaFolderViewPage.SORT_BY_FILENAME))
                {
                    return o1.getName().compareTo(o2.getName());
                }
                else if (sorting.equals(MediaFolderViewPage.SORT_BY_MODIFICATIONDATE))
                {
                    Calendar o1c = o1.getMetaData().getModificationDate() == null
                        ? o1.getMetaData().getCreationDate()
                        : o1.getMetaData().getModificationDate();
                    Calendar o2c = o2.getMetaData().getModificationDate() == null
                        ? o2.getMetaData().getCreationDate()
                        : o2.getMetaData().getModificationDate();
                    return o1c.compareTo(o2c);
                }
                else if (sorting.equals(MediaFolderViewPage.SORT_BY_USAGE))
                {
                    Integer nPages1 = mapNpages.get(o1.getHandle());
                    Integer nPages2 = mapNpages.get(o1.getHandle());
                    if (nPages1 == null)
                    {
                        try
                        {
                            nPages1 = mcm.getUsedInWebPages(o1.getUUID()).size();
                        }
                        catch (InvalidQueryException e)
                        {
                            nPages1 = 0;
                            log.error("Exception getting web pages for media {}", o1.getHandle(), e);
                        }
                        catch (RepositoryException e)
                        {
                            nPages1 = 0;
                            log.error("Exception getting web pages for media {}", o1.getHandle(), e);
                        }
                        mapNpages.put(o1.getHandle(), nPages1);
                    }
                    if (nPages2 == null)
                    {
                        try
                        {
                            nPages2 = mcm.getUsedInWebPages(o2.getUUID()).size();
                        }
                        catch (InvalidQueryException e)
                        {
                            nPages2 = 0;
                            log.error("Exception getting web pages for media {}", o2.getHandle(), e);
                        }
                        catch (RepositoryException e)
                        {
                            nPages2 = 0;
                            log.error("Exception getting web pages for media {}", o2.getHandle(), e);
                        }
                        mapNpages.put(o2.getHandle(), nPages2);
                    }
                    return nPages1.compareTo(nPages2);
                }
                else
                {
                    try
                    {
                        return (o1.getIndex() >= o2.getIndex()) ? 1 : -1;
                    }
                    catch (RepositoryException e)
                    {
                        return 0;
                    }
                }
            }

        });

        return medias;
    }

    /**
     * Search media
     * @param text text to search
     * @param type if specified restricts the search to the type
     * @return found medias
     * @throws RepositoryException exception working on repository
     */
    public Collection<Content> search(String text, final String type) throws RepositoryException
    {
        return find(null, type, text, true);
    }

    @SuppressWarnings("unchecked")
    public Collection<Content> find(String path, String type, String search, boolean recursive)
        throws RepositoryException
    {
        QueryManager qm = MgnlContext.getQueryManager(MediaModule.REPO);
        StringBuffer sbQuery = new StringBuffer("/jcr:root/");
        path = StringUtils.removeEnd(StringUtils.removeStart(StringUtils.trimToEmpty(path), "/"), "/");
        if (StringUtils.isNotEmpty(path))
        {
            sbQuery.append(ISO9075.encodePath(path)).append('/');
        }
        if (recursive)
        {
            sbQuery.append('/');
        }
        sbQuery.append("element(*," + MediaConfigurationManager.MEDIA.getSystemName() + ")");
        List<String> clauses = new ArrayList<String>();
        if (StringUtils.isNotBlank(search))
        {
            clauses.add("jcr:contains(.,'" + StringUtils.replace(search, "'", "''") + "')");
        }
        if (StringUtils.isNotBlank(type))
        {
            clauses.add("@type='" + type + "'");
        }
        if (!clauses.isEmpty())
        {
            sbQuery.append('[').append(StringUtils.join(clauses, " and ")).append(']');
        }
        if (StringUtils.isNotBlank(search))
        {
            sbQuery.append(" order by @jcr:score descending");
        }
        Query q = qm.createQuery(new String(sbQuery), Query.XPATH);
        QueryResult qr = q.execute();
        return qr.getContent(MediaConfigurationManager.MGNL_MEDIA_TYPE);
    }

    /**
     * Get the type configuration for a media
     * @param media media
     * @return type configuration
     */
    public MediaTypeConfiguration getMediaTypeConfigurationFromMedia(Content media)
    {
        try
        {
            if (!media.getItemType().equals(MEDIA))
            {
                return null;
            }
        }
        catch (RepositoryException e)
        {
            log.error("Error getting item type on node {} module media", media.getHandle(), e);
            return null;
        }

        return types.get(NodeDataUtil.getString(media, "type"));
    }

    /**
     * Get uri mapping for repo
     * @return uri mapping for repo
     */
    @SuppressWarnings("unchecked")
    public String getURIMappingPrefix()
    {
        for (URI2RepositoryMapping mapping : (Collection<URI2RepositoryMapping>) URI2RepositoryManager
            .getInstance()
            .getMappings())
        {
            if (mapping.getRepository().equals(MediaModule.REPO))
            {
                return mapping.getURIPrefix();
            }
        }
        return StringUtils.EMPTY;
    }
}
