package eu.dnetlib.functionality.index.solr;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;

import javax.xml.transform.TransformerException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.BinaryResponseWriter;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryResponse;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.servlet.SolrRequestParsers;
import org.dom4j.DocumentException;
import org.springframework.beans.factory.annotation.Required;

import com.google.common.collect.Lists;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.functionality.index.solr.utils.IndexMap;
import eu.dnetlib.functionality.index.solr.utils.IndexSchemaFactory;

/**
 * 
 * @author claudio
 *
 */
public class LocalServer extends SolrServer {


	private static final long serialVersionUID = 280094196781423454L;

	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(LocalServer.class);
	
	protected CoreContainer coreContainer;
	
	private SolrRequestParsers _parser;
	
	private IndexSchemaFactory schemaFactory;
	
	private SolrConfig solrConfig;
	
	private String solrHomeDir;
	
	private String solrDataDir;
	
	/**
	 * Initializes LocalServer.
	 * 
	 * @throws IOException
	 * @throws DocumentException
	 * @throws TransformerException
	 */
	public void init() throws IOException, DocumentException, TransformerException {
		log.info("Initializing Solr LocalServer [home dir: " + 
				solrHomeDir + ", data dir: " + solrDataDir + "]");

		coreContainer = new CoreContainer(solrHomeDir);
		_parser = new SolrRequestParsers( null );
	}
	
	/**
	 * Adds an index.
	 * 
	 * @param idxId
	 * @param fields
	 * @throws IOException
	 * @throws DocumentException
	 * @throws TransformerException
	 * @throws ISLookUpException 
	 * @throws ISLookUpDocumentNotFoundException 
	 */
	public void createIndex(final String indexName, final String fields) 
		throws IOException, DocumentException, TransformerException, ISLookUpDocumentNotFoundException, ISLookUpException {
		
		if (coreContainer.getCore(indexName) != null)
			throw new SolrException(ErrorCode.SERVER_ERROR, "Solr server could not be created, server id already exists: " + indexName);
		else {
			createIndex(schemaFactory.getSchema(indexName, fields), indexName);
		}
	}
	
	/**
	 * Internally adds an index by adding a new SolrCore to the coreContainer.
	 * 
	 * @param idxId
	 * @param indexSchema
	 */
	private void createIndex(final IndexSchema schema, final String indexName) {
				
		final CoreDescriptor coreDescriptor = new CoreDescriptor(coreContainer, indexName, indexName);
		final String idxDataDir = solrDataDir + IOUtils.DIR_SEPARATOR_UNIX + indexName;
		final SolrCore core = new SolrCore(indexName, idxDataDir, solrConfig, schema, coreDescriptor);
		coreContainer.register(indexName, core, true);
	}
	
	/**
	 * Resumes previously created indexes.
	 * 
	 * @throws TransformerException 
	 * @throws DocumentException 
	 * @throws IOException 
	 * @throws ISLookUpException 
	 * @throws ISLookUpDocumentNotFoundException 
	 */
	public List<String> resumeIndexes() throws ISLookUpDocumentNotFoundException, ISLookUpException, IOException, DocumentException, TransformerException { 

		File fileDataDir = new File(solrDataDir);
		
		log.info(solrDataDir + " exists, looking for previously created indexes...");
		
		List<String> indexNames = Lists.newArrayList();
		
		if (fileDataDir.exists()) {
			for(File indexDir: fileDataDir.listFiles()) {
				
				final String indexName = indexDir.getName(); 
				log.info("resuming index: " + indexName + ", path: " + indexDir.getAbsolutePath());
				
				createIndex(schemaFactory.getSchema(indexName), indexName);
				indexNames.add(indexName);
			}
		}
		
		return indexNames;
	}
	
	/**
	 * Get the core names
	 * @return
	 * 		an array containing the core names 
	 */
	public String[] getIndexes() {
		final int size = coreContainer.getCoreNames().size();
		return (String[]) coreContainer.getCoreNames().toArray(new String[size]);
		
	}
	
	////////////////////////////////// 

	public UpdateResponse commit(final String indexName) 
		throws SolrServerException, IOException {
		
		return namedUpdateRequest(indexName).setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true).process(this);
	}
	
	public UpdateResponse optimize(final String indexName) 
		throws SolrServerException, IOException {
	
		return namedUpdateRequest(indexName).setAction(AbstractUpdateRequest.ACTION.OPTIMIZE, true, true).process(this);
	}	
	
	public UpdateResponse add(final SolrInputDocument doc, final String indexName) 
		throws SolrServerException, IOException {
		
		return namedUpdateRequest(indexName).add(doc).process(this);
	}
	
	public UpdateResponse add(final Collection<SolrInputDocument> docs, final String indexName) 
		throws SolrServerException, IOException {
		
		return namedUpdateRequest(indexName).add(docs).process(this);
	}
	
	public UpdateResponse deleteByQuery(final String query, final String indexName) 
		throws SolrServerException, IOException {
		
		return namedUpdateRequest(indexName).deleteByQuery(query).process(this);
	}
	
	/*
	public QueryResponse query(SolrParams params) 
		throws SolrServerException {

		log.info("custom query: " + params.toString());
		
		return new QueryRequest( params ).process( this );
	}
	*/
	
	/**
	 * Helper method to obtain a request with the index.id param
	 * @param idxId
	 * @return
	 */
	private UpdateRequest namedUpdateRequest(final String indexName) {
		
		UpdateRequest req = new UpdateRequest();
		req.setParam(IndexMap.INDEXNAME_PARAM, indexName);
		return req;
	}
	
	//////////// inspiration taken from EmbeddedSolrServer ////////////
	  
	@Override
	public NamedList<Object> request(SolrRequest request) throws SolrServerException, IOException {
		String path = request.getPath();
		if( path == null || !path.startsWith( "/" ) ) {
			path = "/select";
		}
		
		String coreName = request.getParams().get(IndexMap.INDEXNAME_PARAM);
		//log.info("got request for solr core: " + coreName);
		
		// Check for cores action
		SolrCore core =  coreContainer.getCore( coreName );
		if( core == null ) {
			throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "No such index: " + coreName );
		}

		SolrParams params = request.getParams();

		if( params == null ) {
			params = new ModifiableSolrParams();
		}
		
		// Extract the handler from the path or params
		SolrRequestHandler handler = core.getRequestHandler( path );
		if( handler == null ) {
			if( "/select".equals( path ) || "/select/".equalsIgnoreCase( path) ) {
				String qt = params.get( CommonParams.QT );
				handler = core.getRequestHandler( qt );
				if( handler == null ) {
					throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "unknown handler: "+qt);
				}
			}
			// Perhaps the path is to manage the cores
			if( handler == null && coreContainer != null && path.equals( coreContainer.getAdminPath() ) ) {
				handler = coreContainer.getMultiCoreHandler();
			}
		}
		if( handler == null ) {
			core.close();
			throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "unknown handler: "+path );
		}

		try {
			SolrQueryRequest req = _parser.buildRequestFrom( core, params, request.getContentStreams() );
			
			log.debug("SolrQueryRequest >>>>>>> " + req.toString());
			
			req.getContext().put( "path", path );
			SolrQueryResponse rsp = new SolrQueryResponse();
			core.execute( handler, req, rsp );
	
			if( rsp.getException() != null ) {
				throw new SolrServerException( rsp.getException() );
			}

			// Now write it out
			NamedList<Object> normalized = BinaryResponseWriter.getParsedResponse(req, rsp);
			
			req.close();
			return normalized;
		}
		catch( IOException e ) {
			log.error(e);
			throw e;
		}
		catch( Exception e ) {
			throw new SolrServerException(e);
		}
		finally {
			core.close();
		}
	}
	
	/////////////////////////// setters and getters
	
	public IndexSchemaFactory getSchemaFactory() {
		return schemaFactory;
	}
	
	@Required
	public void setSolrDataDir(String solrDataDir) {
		this.solrDataDir = solrDataDir;
	}
	
	@Required
	public void setSolrHomeDir(String solrHomeDir) {
		this.solrHomeDir = solrHomeDir;
	}
	
	@Required
	public void setSolrConfig(SolrConfig solrConfig) {
		this.solrConfig = solrConfig;
	}
	
	@Required
	public void setSchemaFactory(IndexSchemaFactory schemaFactory) {
		this.schemaFactory = schemaFactory;
	}
	
}
