/*
 *  Copyright 2015 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.ametys.cms.indexing.solr;

import java.io.IOException;
import java.util.Set;

import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.components.ContextHelper;
import org.apache.cocoon.environment.Request;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;

import org.ametys.cms.content.indexing.solr.SolrIndexer;
import org.ametys.cms.indexing.IndexingException;
import org.ametys.cms.indexing.WorkspaceIndexer;
import org.ametys.cms.search.solr.SolrClientProvider;
import org.ametys.core.ui.Callable;
import org.ametys.plugins.repository.RepositoryConstants;
import org.ametys.plugins.repository.provider.AbstractRepository;
import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
import org.ametys.plugins.repository.provider.WorkspaceSelector;
import org.ametys.runtime.plugin.component.AbstractLogEnabled;

/**
 * Component indexing a workspace in a Solr server.
 */
public class SolrWorkspaceIndexer extends AbstractLogEnabled implements WorkspaceIndexer, Component, Serviceable, Contextualizable
{
    /** The repository. */
    protected Repository _repository;
    
    /** The solr indexer. */
    protected SolrIndexer _solrIndexer;
    
    /** The workspace selector. */
    protected WorkspaceSelector _workspaceSelector;
    
    /** Additional documents provider extension point. */
    protected DocumentProviderExtensionPoint _docProviderEP;
    
    /** The Solr client provider */
    protected SolrClientProvider _solrClientProvider;
    
    /** The component context. */
    protected Context _context;
    
    @Override
    public void service(ServiceManager manager) throws ServiceException
    {
        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
        _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE);
        _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE);
        _docProviderEP = (DocumentProviderExtensionPoint) manager.lookup(DocumentProviderExtensionPoint.ROLE);
        _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE);
    }
    
    @Override
    public void contextualize(Context context) throws ContextException
    {
        _context = context;
    }
    
    @Override
    @Callable
    public void indexAllWorkspaces() throws IndexingException
    {
        String[] workspaceNames;
        try
        {
            // Get all the workspaces and reindex them.
            Session session = _repository.login();
            workspaceNames = session.getWorkspace().getAccessibleWorkspaceNames();
        }
        catch (RepositoryException e)
        {
            getLogger().error("Error while indexing the workspaces.", e);
            throw new IndexingException("Error while indexing the workspaces.", e);
        }
        
        // Before sending schema, all cores need to exist
        ensureCoresExists(workspaceNames);
        
        _sendSchema();
        
        for (String workspaceName : workspaceNames)
        {
            _index(workspaceName, false, false); // no need to ensure core exists, nor sending schema, as it was done just before
        }
    }
    
    // Send schema (using default update Solr client)
    private void _sendSchema() throws IndexingException
    {
        Request request = ContextHelper.getRequest(_context);
        // Retrieve the current workspace.
        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
        try
        {
            // Force the default workspace.
            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
            _solrIndexer.sendSchema();
        }
        catch (IOException | SolrServerException e)
        {
            getLogger().error("Error while sending schema.", e);
            throw new IndexingException("Error while sending schema.", e);
        }
        finally
        {
            // Restore context
            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
        }
    }
    
    @Override
    public void index(String workspaceName) throws IndexingException
    {
        _index(workspaceName, true, true);
    }
    
    private void _index(String workspaceName, boolean ensureCoreExists, boolean sendSchema) throws IndexingException
    {
        if (ensureCoreExists)
        {
            // Create the core corresponding to the workspace if it doesn't exist.
            ensureCoreExists(workspaceName);
        }
        
        getLogger().info("Start indexing workspace {}...", workspaceName);
        
        _forceWorkspaceAndDoIndex(workspaceName, sendSchema);
        
        getLogger().info("Successfully indexed workspace {}", workspaceName);
    }
    
    private void _forceWorkspaceAndDoIndex(String workspaceName, boolean sendSchema) throws IndexingException
    {
        Request request = ContextHelper.getRequest(_context);
        
        // Retrieve the current workspace.
        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
        
        try
        {
            // Force the workspace.
            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName);
            
            if (sendSchema)
            {
                try
                {
                    _solrIndexer.sendSchema();
                }
                catch (IOException | SolrServerException e)
                {
                    getLogger().error("Error while sending schema.", e);
                    throw new IndexingException("Error while sending schema.", e);
                }
            }
            
            doIndex(workspaceName);
        }
        finally
        {
            // Restore context
            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
        }
        
    }
    
    /**
     * Index the given workspace.
     * @param workspaceName The workspace name.
     * @throws IndexingException If an error occurs indexing the workspace.
     */
    protected void doIndex(String workspaceName) throws IndexingException
    {
        try
        {
            SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName, false);
            
            // First, unindex all documents.
            _solrIndexer.unindexAllDocuments(workspaceName, solrClient);
            
            // Index all contents.
            _solrIndexer.indexAllContents(workspaceName, true, solrClient);
            
            // Index all resources.
            _solrIndexer.indexAllResources(workspaceName, solrClient);
            
            // Eventually index additional documents.
            indexAdditionalDocuments(workspaceName, solrClient);
            
            // Commit changes
            _solrIndexer.commit(workspaceName, solrClient);
            // When done indexing, optimize.
            _solrIndexer.optimize(workspaceName, solrClient);
        }
        catch (Exception e)
        {
            getLogger().error("Error indexing the workspace '" + workspaceName + "'.", e);
            throw new IndexingException("Error indexing the workspace '" + workspaceName + "'.", e);
        }
    }
    
    /**
     * Index additional documents provided by the extensions.
     * @param workspaceName The workspace name.
     * @param solrClient The solr client to use
     * @throws IndexingException If an error occurs while indexing.
     */
    protected void indexAdditionalDocuments(String workspaceName, SolrClient solrClient) throws IndexingException
    {
        for (String id : _docProviderEP.getExtensionsIds())
        {
            DocumentProvider docProvider = _docProviderEP.getExtension(id);
            docProvider.indexDocuments(workspaceName, solrClient);
        }
    }
    
    /**
     * Create the given cores if they do not exist.
     * @param coreNames The core names.
     * @throws IndexingException If an error occurs while checking if cores exist.
     */
    protected void ensureCoresExists(String[] coreNames) throws IndexingException
    {
        for (String coreName : coreNames)
        {
            ensureCoreExists(coreName);
        }
    }
    
    /**
     * Create the given core if it doesn't exist.
     * @param coreName The core name.
     * @throws IndexingException If an error occurs while checking if core exists.
     */
    protected void ensureCoreExists(String coreName) throws IndexingException
    {
        try
        {
            Set<String> coreNames = _solrIndexer.getCoreNames();
            if (!coreNames.contains(coreName))
            {
                _solrIndexer.createCore(coreName);
            }
        }
        catch (IOException | SolrServerException e)
        {
            String msg = String.format("Error while checking if core '%s' exists.", coreName);
            getLogger().error(msg, e);
            throw new IndexingException(msg, e);
        }
    }
}