/*
 *
 *  * Copyright 2014 Orient Technologies.
 *  *
 *  * 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 com.orientechnologies.lucene.collections;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.lucene.manager.OLuceneIndexManagerAbstract;
import com.orientechnologies.lucene.query.QueryContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.OContextualRecordId;
import com.orientechnologies.orient.core.record.impl.ODocument;
import org.apache.lucene.document.Document;
import org.apache.lucene.facet.*;
import org.apache.lucene.facet.taxonomy.FastTaxonomyFacetCounts;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;

import java.io.IOException;
import java.util.*;

/**
 * Created by Enrico Risa on 28/10/14.
 */
public class LuceneResultSet implements Set<OIdentifiable> {

  private TopDocs                     topDocs;
  private Query                       query;
  private OLuceneIndexManagerAbstract manager;
  private QueryContext                queryContext;
  private static Integer              PAGE_SIZE = 50;

  public LuceneResultSet(OLuceneIndexManagerAbstract manager, QueryContext queryContext) {
    this.manager = manager;
    this.queryContext = queryContext;
    this.query = queryContext.query;
    fetchFirstBatch();
    fetchFacet();
  }

  private void fetchFacet() {
    if (queryContext.facet) {
      FacetsCollector facetsCollector = new FacetsCollector(true);

      try {

        String[] pathFacet = null;
        if (queryContext.isDrillDown()) {
          DrillDownQuery drillDownQuery = new DrillDownQuery(queryContext.getFacetConfig(), query);
          String[] path = queryContext.getDrillDownQuery().split(":");
          pathFacet = path[1].split("/");
          drillDownQuery.add(path[0], pathFacet);
          FacetsCollector.search(queryContext.searcher, drillDownQuery, PAGE_SIZE, facetsCollector);
        } else {
          FacetsCollector.search(queryContext.searcher, query, PAGE_SIZE, facetsCollector);
        }

        Facets facets = new FastTaxonomyFacetCounts(queryContext.reader, queryContext.getFacetConfig(), facetsCollector);

        FacetResult facetResult = null;
        if (pathFacet != null) {
          facetResult = facets.getTopChildren(PAGE_SIZE, queryContext.getFacetField(), pathFacet);
        } else {
          facetResult = facets.getTopChildren(PAGE_SIZE, queryContext.getFacetField());
        }

        if (facetResult != null) {
          List<ODocument> documents = new ArrayList<ODocument>();
          // for (FacetResult facetResult : res) {

          ODocument doc = new ODocument();

          doc.field("childCount", facetResult.childCount);
          doc.field("value", facetResult.value);
          doc.field("dim", facetResult.dim);
          List<ODocument> labelsAndValue = new ArrayList<ODocument>();
          for (LabelAndValue labelValue : facetResult.labelValues) {
            ODocument doc1 = new ODocument();
            doc1.field("label", labelValue.label);
            doc1.field("value", labelValue.value);
            labelsAndValue.add(doc1);

          }
          doc.field("labelsValue", labelsAndValue);
          documents.add(doc);
          queryContext.context.setVariable("$facet", documents);
        }
        // }

      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public int size() {
    return topDocs.totalHits;
  }

  private void fetchFirstBatch() {
    try {

      switch (queryContext.cfg) {

      case NO_FILTER_NO_SORT:
        topDocs = queryContext.searcher.search(query, PAGE_SIZE);
        break;
      case FILTER_SORT:
        topDocs = queryContext.searcher.search(query, queryContext.filter, PAGE_SIZE, queryContext.sort);
        break;
      case FILTER:
        topDocs = queryContext.searcher.search(query, queryContext.filter, PAGE_SIZE);
        break;
      case SORT:
        topDocs = queryContext.searcher.search(query, PAGE_SIZE, queryContext.sort);
        break;
      }
    } catch (IOException e) {
      OLogManager.instance().error(this, "Error on fetching document by query '%s' to Lucene index", e, query);
    }
  }

  @Override
  public boolean isEmpty() {
    return size() == 0;
  }

  @Override
  public boolean contains(Object o) {
    throw new UnsupportedOperationException();
  }

  @Override
  public Iterator<OIdentifiable> iterator() {
    return new OLuceneResultSetIterator();
  }

  @Override
  public Object[] toArray() {
    throw new UnsupportedOperationException();
  }

  @Override
  public <T> T[] toArray(T[] a) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean add(OIdentifiable oIdentifiable) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean remove(Object o) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean containsAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean addAll(Collection<? extends OIdentifiable> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean retainAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean removeAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void clear() {
    throw new UnsupportedOperationException();
  }

  private class OLuceneResultSetIterator implements Iterator<OIdentifiable> {

    ScoreDoc[]  array;
    private int index;
    private int localIndex;
    private int totalHits;

    public OLuceneResultSetIterator() {
      totalHits = topDocs.totalHits;
      index = 0;
      localIndex = 0;
      array = topDocs.scoreDocs;
      manager.sendTotalHits(queryContext.context, topDocs);
    }

    @Override
    public boolean hasNext() {
      return index < totalHits;
    }

    @Override
    public OIdentifiable next() {
      if (localIndex == array.length) {
        localIndex = 0;
        fetchMoreResult();
      }
      final ScoreDoc score = array[localIndex++];
      Document ret = null;
      OContextualRecordId res = null;
      try {
        ret = queryContext.searcher.doc(score.doc);
        String rId = ret.get(OLuceneIndexManagerAbstract.RID);
        res = new OContextualRecordId(rId);
        manager.onRecordAddedToResultSet(queryContext, res, ret, score);
      } catch (IOException e) {
        e.printStackTrace();
      }
      index++;
      return res;
    }

    private void fetchMoreResult() {

      TopDocs topDocs = null;
      try {

        switch (queryContext.cfg) {

        case NO_FILTER_NO_SORT:
          topDocs = queryContext.searcher.searchAfter(array[array.length - 1], query, PAGE_SIZE);
          break;
        case FILTER_SORT:
          topDocs = queryContext.searcher.searchAfter(array[array.length - 1], query, queryContext.filter, PAGE_SIZE,
              queryContext.sort);
          break;
        case FILTER:
          topDocs = queryContext.searcher.searchAfter(array[array.length - 1], query, queryContext.filter, PAGE_SIZE);
          break;
        case SORT:
          topDocs = queryContext.searcher.searchAfter(array[array.length - 1], query, PAGE_SIZE, queryContext.sort);
          break;
        }
        array = topDocs.scoreDocs;
      } catch (IOException e) {
        OLogManager.instance().error(this, "Error on fetching document by query '%s' to Lucene index", e, query);
      }

    }

    @Override
    public void remove() {

    }
  }
}