package leigh.notek.store;

import leigh.mecha.io.FileUtil;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MMapDirectory;

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

public class ZettelIndex {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(ZettelIndex.class);
    private final static String KEY_TXT = "txt";
    private final static String KEY_ID = "id";
    private final Directory index;
    private final StandardAnalyzer analyzer = new StandardAnalyzer();

    public ZettelIndex() throws IOException {
        File tmpDir = FileUtil.createTmpDirectory("notek", ".tmp");
        logger.info("Created temporary index. [path: {}]", tmpDir);
        index = new MMapDirectory(tmpDir.toPath());
    }

    private Document makeDoc(Zettel zettel) {
        Document doc = new Document();
        String txt = zettel.getTxt();

        doc.add(new StringField(KEY_ID, zettel.getId(), Field.Store.YES));
        if (txt != null) doc.add(new TextField(KEY_TXT, txt, Field.Store.YES));
        return doc;
    }

    public void load(ZettelStore store) throws IOException {
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        try (IndexWriter w = new IndexWriter(index, config)) {
            ArrayList<String> ids = new ArrayList<>();
            store.getIds(ids);
            for (String id : ids) {
                String txt = store.getZettel(id);
                w.addDocument(makeDoc(new Zettel(id, txt)));
            }
        }
    }

    public void update(ZettelStore store, String id) throws IOException {
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        try (IndexWriter w = new IndexWriter(index, config)) {
            String txt = store.getZettel(id);
            w.updateDocument(new Term(KEY_ID, id), makeDoc(new Zettel(id, txt)));
        }
    }

    public void remove(ZettelStore store, String id) throws IOException {
        if (id != null) {
            IndexWriterConfig config = new IndexWriterConfig(analyzer);
            try (IndexWriter w = new IndexWriter(index, config)) {
                w.deleteDocuments(new Term(KEY_ID, id));
            }
        } else {
            logger.error("remove() called with null id.");
        }
    }

    public void search(List<String> dst, String query) throws ParseException, IOException {
        Query q = new QueryParser(KEY_TXT, analyzer).parse(query);
        int hitsPerPage = 1000;
        IndexReader reader = DirectoryReader.open(index);
        IndexSearcher searcher = new IndexSearcher(reader);
        TopDocs docs = searcher.search(q, hitsPerPage);
        ScoreDoc[] hits = docs.scoreDocs;
        System.out.println("Found " + hits.length + " hits.");

        for (ScoreDoc hit : hits) {
            int docId = hit.doc;
            Document d = searcher.doc(docId);
            dst.add(d.get(KEY_ID));
        }
    }
}
