/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 lucy

/*

#define C_LUCY_HITS

#include "Lucy/Search/Collector.h"
#include "Lucy/Search/Collector/SortCollector.h"
#include "Lucy/Search/Hits.h"
#include "Lucy/Search/IndexSearcher.h"
#include "Lucy/Search/Query.h"
#include "Lucy/Search/Compiler.h"
#include "Lucy/Search/Searcher.h"
#include "Lucy/Search/QueryParser.h"
#include "Lucy/Search/ANDQuery.h"
#include "Lucy/Search/ORQuery.h"
#include "Lucy/Search/ANDMatcher.h"
#include "Lucy/Search/MatchDoc.h"
#include "Lucy/Search/ORMatcher.h"
#include "Lucy/Search/SeriesMatcher.h"
#include "Lucy/Search/SortRule.h"
#include "Lucy/Search/SortSpec.h"
#include "Lucy/Search/TopDocs.h"
#include "Lucy/Document/HitDoc.h"
#include "Lucy/Index/IndexReader.h"
#include "LucyX/Search/MockMatcher.h"
#include "Clownfish/Blob.h"
#include "Clownfish/Hash.h"
#include "Clownfish/HashIterator.h"
#include "Clownfish/Vector.h"

static inline void
float32_set(float *floats, size_t i, float value) {
	floats[i] = value;
}

*/
import "C"
import "unsafe"

import "git-wip-us.apache.org/repos/asf/lucy-clownfish.git/runtime/go/clownfish"

type HitsIMP struct {
	clownfish.ObjIMP
	err error
}

type MatcherIMP struct {
	clownfish.ObjIMP
	err error
}

func OpenIndexSearcher(index interface{}) (obj IndexSearcher, err error) {
	indexC := (*C.cfish_Obj)(clownfish.GoToClownfish(index, unsafe.Pointer(C.CFISH_OBJ), false))
	defer C.cfish_decref(unsafe.Pointer(indexC))
	err = clownfish.TrapErr(func() {
		cfObj := C.lucy_IxSearcher_new(indexC)
		obj = WRAPIndexSearcher(unsafe.Pointer(cfObj))
	})
	return obj, err
}

// Read data into the supplied doc.
func (s *SearcherIMP) ReadDoc(docID int32, doc interface{}) error {
	self := (*C.lucy_Searcher)(clownfish.Unwrap(s, "s"))
	class := C.cfish_Obj_get_class((*C.cfish_Obj)(unsafe.Pointer(self)))
	if class == C.LUCY_INDEXSEARCHER {
		ixReader := C.LUCY_IxSearcher_Get_Reader((*C.lucy_IndexSearcher)(unsafe.Pointer(self)))
		cfStr := (*C.cfish_String)(clownfish.GoToClownfish("Lucy::Index::DocReader", unsafe.Pointer(C.CFISH_STRING), false))
		defer C.cfish_decref(unsafe.Pointer(cfStr))
		docReader := C.LUCY_IxReader_Fetch(ixReader, cfStr)
		if docReader == nil {
			return clownfish.NewErr("No DocReader available")
		}
		docReaderGo := clownfish.WRAPAny(unsafe.Pointer(C.cfish_incref(unsafe.Pointer(docReader)))).(DocReader)
		return docReaderGo.ReadDoc(docID, doc)
	} else {
		return clownfish.NewErr("Support for ReadDoc not implemented")
	}
}

func (s *SearcherIMP) FetchDoc(docID int32) (doc HitDoc, err error) {
	err = clownfish.TrapErr(func() {
		self := (*C.lucy_Searcher)(clownfish.Unwrap(s, "s"))
		docC := C.LUCY_Searcher_Fetch_Doc(self, C.int32_t(docID))
		doc = WRAPHitDoc(unsafe.Pointer(docC))
	})
	return doc, err
}

func (s *SearcherIMP) fetchDocVec(docID int32) (dv DocVector, err error) {
	err = clownfish.TrapErr(func() {
		self := (*C.lucy_Searcher)(clownfish.Unwrap(s, "s"))
		dvC := C.LUCY_Searcher_Fetch_Doc_Vec(self, C.int32_t(docID))
		dv = WRAPDocVector(unsafe.Pointer(dvC))
	})
	return dv, err
}

func (s *SearcherIMP) Close() error {
	return clownfish.TrapErr(func() {
		self := (*C.lucy_Searcher)(clownfish.Unwrap(s, "s"))
		C.LUCY_Searcher_Close(self)
	})
}

func (s *SearcherIMP) Hits(query interface{}, offset uint32, numWanted uint32,
	sortSpec SortSpec) (hits Hits, err error) {
	self := (*C.lucy_Searcher)(clownfish.Unwrap(s, "s"))
	sortSpecC := (*C.lucy_SortSpec)(clownfish.UnwrapNullable(sortSpec))
	queryC := (*C.cfish_Obj)(clownfish.GoToClownfish(query, unsafe.Pointer(C.CFISH_OBJ), false))
	defer C.cfish_decref(unsafe.Pointer(queryC))
	err = clownfish.TrapErr(func() {
		hitsC := C.LUCY_Searcher_Hits(self, queryC,
			C.uint32_t(offset), C.uint32_t(numWanted), sortSpecC)
		hits = WRAPHits(unsafe.Pointer(hitsC))
	})
	return hits, err
}

func (s *SearcherIMP) topDocs(query Query, numWanted uint32,
	sortSpec SortSpec) (topDocs TopDocs, err error) {
	err = clownfish.TrapErr(func() {
		self := (*C.lucy_Searcher)(clownfish.Unwrap(s, "s"))
		sortSpecC := (*C.lucy_SortSpec)(clownfish.UnwrapNullable(sortSpec))
		queryC := (*C.lucy_Query)(clownfish.Unwrap(query, "query"))
		topDocsC := C.LUCY_Searcher_Top_Docs(self, queryC,
			C.uint32_t(numWanted), sortSpecC)
		topDocs = WRAPTopDocs(unsafe.Pointer(topDocsC))
	})
	return topDocs, err
}

func NewQueryParser(schema Schema, fields []string) QueryParser {
	return NewORParser(schema, fields)
}

func NewORParser(schema Schema, fields []string) QueryParser {
	return doNewQueryParser(schema, "OR", fields)
}

func NewANDParser(schema Schema, fields []string) QueryParser {
	return doNewQueryParser(schema, "AND", fields)
}

func doNewQueryParser(schema Schema, defaultBoolop string, fields []string) QueryParser {
	schemaCF := (*C.lucy_Schema)(clownfish.Unwrap(schema, "schema"))
	defaultBoolopCF := (*C.cfish_String)(clownfish.GoToClownfish(defaultBoolop, unsafe.Pointer(C.CFISH_STRING), true))
	defer C.cfish_decref(unsafe.Pointer(defaultBoolopCF))
	fieldsCF := stringSliceToVec(fields)
	defer C.cfish_decref(unsafe.Pointer(fieldsCF))
	retvalCF := C.lucy_QParser_new(schemaCF, nil, defaultBoolopCF, fieldsCF)
	return clownfish.WRAPAny(unsafe.Pointer(retvalCF)).(QueryParser)
}

func (qp *QueryParserIMP) MakePhraseQuery(field string, terms []interface{}) PhraseQuery {
	return NewPhraseQuery(field, terms)
}

func (qp *QueryParserIMP) MakeANDQuery(children []Query) ANDQuery {
	return NewANDQuery(children)
}

func (qp *QueryParserIMP) MakeORQuery(children []Query) ORQuery {
	return NewORQuery(children)
}

func (q *QueryParserIMP) getFields() []string {
	self := (*C.lucy_QueryParser)(clownfish.Unwrap(q, "q"))
	retvalCF := C.LUCY_QParser_Get_Fields(self)
	return vecToStringSlice(retvalCF)
}

type setScorer interface {
	SetScore(float32)
}

func (h *HitsIMP) Next(hit interface{}) bool {
	self := (*C.lucy_Hits)(clownfish.Unwrap(h, "h"))
	ivars := C.lucy_Hits_IVARS(self)
	matchDoc := (*C.lucy_MatchDoc)(unsafe.Pointer(
		C.CFISH_Vec_Fetch(ivars.match_docs, C.size_t(ivars.offset))))
	ivars.offset += 1

	if matchDoc == nil {
		// Bail if there aren't any more *captured* hits.  (There may be
		// more total hits.)
		return false
	} else {
		// Lazily fetch HitDoc, set score.
		searcher := clownfish.WRAPAny(unsafe.Pointer(C.cfish_incref(
			unsafe.Pointer(ivars.searcher)))).(Searcher)
		docID := int32(C.LUCY_MatchDoc_Get_Doc_ID(matchDoc))
		err := searcher.ReadDoc(docID, hit)
		if err != nil {
			h.err = err
			return false
		}
		if ss, ok := hit.(setScorer); ok {
			ss.SetScore(float32(C.LUCY_MatchDoc_Get_Score(matchDoc)))
		}
		return true
	}
}

func (obj *HitsIMP) Error() error {
	return obj.err
}

func NewFieldSortRule(field string, reverse bool) SortRule {
	fieldC := clownfish.GoToClownfish(field, unsafe.Pointer(C.CFISH_STRING), false)
	cfObj := C.lucy_SortRule_new(C.lucy_SortRule_FIELD, (*C.cfish_String)(fieldC), C.bool(reverse))
	return WRAPSortRule(unsafe.Pointer(cfObj))
}

func NewDocIDSortRule(reverse bool) SortRule {
	cfObj := C.lucy_SortRule_new(C.lucy_SortRule_DOC_ID, nil, C.bool(reverse))
	return WRAPSortRule(unsafe.Pointer(cfObj))
}

func NewScoreSortRule(reverse bool) SortRule {
	cfObj := C.lucy_SortRule_new(C.lucy_SortRule_SCORE, nil, C.bool(reverse))
	return WRAPSortRule(unsafe.Pointer(cfObj))
}

func NewSortSpec(rules []SortRule) SortSpec {
	vec := clownfish.NewVector(len(rules))
	for _, rule := range rules {
		vec.Push(rule)
	}
	cfObj := C.lucy_SortSpec_new((*C.cfish_Vector)(clownfish.Unwrap(vec, "rules")))
	return WRAPSortSpec(unsafe.Pointer(cfObj))
}

func (spec *SortSpecIMP) GetRules() []SortRule {
	self := (*C.lucy_SortSpec)(clownfish.Unwrap(spec, "spec"))
	vec := C.LUCY_SortSpec_Get_Rules(self)
	length := int(C.CFISH_Vec_Get_Size(vec))
	slice := make([]SortRule, length)
	for i := 0; i < length; i++ {
		elem := C.cfish_incref(unsafe.Pointer(C.CFISH_Vec_Fetch(vec, C.size_t(i))))
		slice[i] = WRAPSortRule(unsafe.Pointer(elem))
	}
	return slice
}

func NewTopDocs(matchDocs []MatchDoc, totalHits uint32) TopDocs {
	vec := clownfish.NewVector(len(matchDocs))
	for _, matchDoc := range matchDocs {
		vec.Push(matchDoc)
	}
	cfObj := C.lucy_TopDocs_new(((*C.cfish_Vector)(clownfish.Unwrap(vec, "matchDocs"))),
		C.uint32_t(totalHits))
	return WRAPTopDocs(unsafe.Pointer(cfObj))
}

func (td *TopDocsIMP) SetMatchDocs(matchDocs []MatchDoc) {
	self := (*C.lucy_TopDocs)(clownfish.Unwrap(td, "td"))
	vec := clownfish.NewVector(len(matchDocs))
	for _, matchDoc := range matchDocs {
		vec.Push(matchDoc)
	}
	C.LUCY_TopDocs_Set_Match_Docs(self, (*C.cfish_Vector)(clownfish.Unwrap(vec, "matchDocs")))
}

func (td *TopDocsIMP) GetMatchDocs() []MatchDoc {
	self := (*C.lucy_TopDocs)(clownfish.Unwrap(td, "td"))
	vec := C.LUCY_TopDocs_Get_Match_Docs(self)
	length := int(C.CFISH_Vec_Get_Size(vec))
	slice := make([]MatchDoc, length)
	for i := 0; i < length; i++ {
		elem := C.cfish_incref(unsafe.Pointer(C.CFISH_Vec_Fetch(vec, C.size_t(i))))
		slice[i] = WRAPMatchDoc(unsafe.Pointer(elem))
	}
	return slice
}

func (q *QueryIMP) MakeCompiler(searcher Searcher, boost float32,
	subordinate bool) (retval Compiler, err error) {
	err = clownfish.TrapErr(func() {
		self := (*C.lucy_Query)(clownfish.Unwrap(q, "q"))
		searcherCF := (*C.lucy_Searcher)(clownfish.Unwrap(searcher, "searcher"))
		retvalCF := C.LUCY_Query_Make_Compiler(self, searcherCF, C.float(boost), C.bool(subordinate))
		if retvalCF != nil {
			retval = clownfish.WRAPAny(unsafe.Pointer(retvalCF)).(Compiler)
		}
	})
	return retval, err
}

func (c *CompilerIMP) MakeMatcher(reader SegReader, needScore bool) (retval Matcher, err error) {
	err = clownfish.TrapErr(func() {
		self := (*C.lucy_Compiler)(clownfish.Unwrap(c, "c"))
		readerCF := (*C.lucy_SegReader)(clownfish.Unwrap(reader, "reader"))
		retvalCF := C.LUCY_Compiler_Make_Matcher(self, readerCF, C.bool(needScore))
		if retvalCF != nil {
			retval = clownfish.WRAPAny(unsafe.Pointer(retvalCF)).(Matcher)
		}
	})
	if err != nil || retval == nil {
		return nil, err
	}
	return retval, err
}

func NewANDQuery(children []Query) ANDQuery {
	vec := clownfish.NewVector(len(children))
	for _, child := range children {
		vec.Push(child)
	}
	childrenC := (*C.cfish_Vector)(unsafe.Pointer(vec.TOPTR()))
	cfObj := C.lucy_ANDQuery_new(childrenC)
	return WRAPANDQuery(unsafe.Pointer(cfObj))
}

func NewORQuery(children []Query) ORQuery {
	vec := clownfish.NewVector(len(children))
	for _, child := range children {
		vec.Push(child)
	}
	childrenC := (*C.cfish_Vector)(unsafe.Pointer(vec.TOPTR()))
	cfObj := C.lucy_ORQuery_new(childrenC)
	return WRAPORQuery(unsafe.Pointer(cfObj))
}

func (m *MatcherIMP) Next() int32 {
	var retval int32
	m.err = clownfish.TrapErr(func() {
		self := (*C.lucy_Matcher)(clownfish.Unwrap(m, "m"))
		retval = int32(C.LUCY_Matcher_Next(self))
	})
	if m.err != nil {
		return 0
	}
	return retval
}

func (m *MatcherIMP) Error() error {
	return m.err
}

func NewANDMatcher(children []Matcher, sim Similarity) ANDMatcher {
	simC := (*C.lucy_Similarity)(clownfish.UnwrapNullable(sim))
	vec := clownfish.NewVector(len(children))
	for _, child := range children {
		vec.Push(child)
	}
	childrenC := (*C.cfish_Vector)(unsafe.Pointer(vec.TOPTR()))
	cfObj := C.lucy_ANDMatcher_new(childrenC, simC)
	return WRAPANDMatcher(unsafe.Pointer(cfObj))
}

func NewORMatcher(children []Matcher) ORMatcher {
	vec := clownfish.NewVector(len(children))
	for _, child := range children {
		vec.Push(child)
	}
	childrenC := (*C.cfish_Vector)(unsafe.Pointer(vec.TOPTR()))
	cfObj := C.lucy_ORMatcher_new(childrenC)
	return WRAPORMatcher(unsafe.Pointer(cfObj))
}

func NewORScorer(children []Matcher, sim Similarity) ORScorer {
	simC := (*C.lucy_Similarity)(clownfish.UnwrapNullable(sim))
	vec := clownfish.NewVector(len(children))
	for _, child := range children {
		vec.Push(child)
	}
	childrenC := (*C.cfish_Vector)(unsafe.Pointer(vec.TOPTR()))
	cfObj := C.lucy_ORScorer_new(childrenC, simC)
	return WRAPORScorer(unsafe.Pointer(cfObj))
}

func NewSeriesMatcher(matchers []Matcher, offsets []int32) SeriesMatcher {
	vec := clownfish.NewVector(len(matchers))
	for _, child := range matchers {
		vec.Push(child)
	}
	i32arr := NewI32Array(offsets)
	cfObj := C.lucy_SeriesMatcher_new(((*C.cfish_Vector)(clownfish.Unwrap(vec, "matchers"))),
		((*C.lucy_I32Array)(clownfish.Unwrap(i32arr, "offsets"))))
	return WRAPSeriesMatcher(unsafe.Pointer(cfObj))
}

func newMockMatcher(docIDs []int32, scores []float32) MockMatcher {
	docIDsconv := NewI32Array(docIDs)
	docIDsCF := (*C.lucy_I32Array)(unsafe.Pointer(docIDsconv.TOPTR()))
	var blob *C.cfish_Blob = nil
	if scores != nil {
		size := len(scores) * 4
		floats := (*C.float)(C.malloc(C.size_t(size)))
		for i := 0; i < len(scores); i++ {
			C.float32_set(floats, C.size_t(i), C.float(scores[i]))
		}
		blob = C.cfish_Blob_new_steal(unsafe.Pointer(floats), C.size_t(size))
		defer C.cfish_decref(unsafe.Pointer(blob))
	}
	matcher := C.lucy_MockMatcher_new(docIDsCF, blob)
	return WRAPMockMatcher(unsafe.Pointer(matcher))
}

func (sc *SortCollectorIMP) PopMatchDocs() []MatchDoc {
	self := (*C.lucy_SortCollector)(clownfish.Unwrap(sc, "sc"))
	matchDocsC := C.LUCY_SortColl_Pop_Match_Docs(self)
	defer C.cfish_decref(unsafe.Pointer(matchDocsC))
	length := int(C.CFISH_Vec_Get_Size(matchDocsC))
	slice := make([]MatchDoc, length)
	for i := 0; i < length; i++ {
		elem := C.cfish_incref(unsafe.Pointer(C.CFISH_Vec_Fetch(matchDocsC, C.size_t(i))))
		slice[i] = WRAPMatchDoc(unsafe.Pointer(elem))
	}
	return slice
}
