package eu.fbk.dh.tint.digimorph;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.CharSource;
import com.google.common.io.Resources;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.mapdb.Serializer;
import org.mapdb.SortedTableMap;
import org.mapdb.volume.MappedFileVol;
import org.mapdb.volume.Volume;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author Giovanni Moretti at Digital Humanities group at FBK.
 * @version 0.4a
 */
public class DigiMorph {

    String model_path = "";
    ExecutorService executor = null;
    List<Future<List<String>>> futures = null;

    Set<Callable<List<String>>> callables = new HashSet<Callable<List<String>>>();
    Volume volume = null;
    SortedTableMap<String, String> map = null;

    private static final Logger LOGGER = LoggerFactory.getLogger(DigiMorph.class);


    public static String getVersion() {
        return DigiMorph.class.getPackage().getImplementationTitle() + "\n"
                + DigiMorph.class.getPackage().getSpecificationVendor() + " - "
                + DigiMorph.class.getPackage().getImplementationVendor() + "\n"
                + "Version: " + DigiMorph.class.getPackage().getSpecificationVersion();
    }

    public DigiMorph() {
        this(null);
    }

    public DigiMorph(String model_path) {
        if (model_path == null) {
            try {
                File file = File.createTempFile("mapdb", "mapdb");
                file.deleteOnExit();
                byte[] bytes = Resources.toByteArray(Resources.getResource("italian.db"));
                Files.write(file.toPath(), bytes);
                model_path = file.getAbsolutePath();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        this.model_path = model_path;
        volume = MappedFileVol.FACTORY.makeVolume(model_path, true);
        this.map = SortedTableMap.open(volume, Serializer.STRING, Serializer.STRING);

    }

    public SortedTableMap<String, String> getMap() {
        return map;
    }

    /**
     * @param token_list list of string containing words.
     * @return list of string containing the results of the Morphological analyzer.
     * @author Giovanni Moretti
     * @version 0.42a
     */

    synchronized public List<String> getMorphology(String token) {
        List<String> list = new ArrayList<>();
        list.add(token);
        return getMorphology(list);
    }

    synchronized public List<String> getMorphology(List<String> token_list) {
        List<String> results = new LinkedList<>();
        int threadsNumber = Runtime.getRuntime().availableProcessors();
        List<List<String>> parts = Lists.partition(token_list, (token_list.size() / threadsNumber) + 1);

        if (token_list.size() > 0) {
            executor = Executors.newFixedThreadPool(parts.size());
        } else {
            LOGGER.warn("No tokens to the morphological analyzer");
            return results;
        }

        callables = new LinkedHashSet<>();

        for (int pts = 0; pts < parts.size(); pts++) {
            callables.add(new DigiMorph_Analizer(parts.get(pts), map));
        }

        try {

            futures = executor.invokeAll(callables);

            executor.shutdown();
            executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);

            executor.shutdownNow();
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            for (Future<List<String>> future : futures) {
                List<String> stringList = future.get();
                results.addAll(stringList);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
//        for (int pts = 0; pts < parts.size(); pts++) {
//            parts.get(pts).clear();
//        }

        return results;
    }

//    Map<String, String> mapcodgram = new HashMap<String, String>();
//    Map<String, String> mapcodfless = new HashMap<String, String>();

    /**
     * This method creates or re-creates the db file with the morphology forms used by the analyzer
     *
     * @param csv_path - String contains the tsv file path
     */

    public static void re_train(File csv_path, File output, boolean include_lemma) {
        if (output.exists()) {
            output.delete();
        }
        Volume volume = MappedFileVol.FACTORY.makeVolume(output.getAbsolutePath(), false);
        SortedTableMap.Sink<String, String> sink =
                SortedTableMap.create(
                        volume,
                        Serializer.STRING, // key serializer
                        Serializer.STRING   // value serializer
                )
                        .pageSize(64 * 1024)
                        .nodeSize(8)
                        .createFromSink();

        SortedMap<String, String> map = new TreeMap<String, String>();
        try {
            Reader in;
            if (csv_path == null) {
                URL resource = Resources.getResource("morph-it_dh_extended.txt");
                CharSource charSource = Resources.asCharSource(resource, Charsets.UTF_8);
                in = charSource.openStream();
            } else {
                in = new FileReader(csv_path);
            }
            Iterable<CSVRecord> records = CSVFormat.TDF.withIgnoreEmptyLines().withQuote('≥').parse(in);
            for (CSVRecord record : records) {

                String feature = record.get(2);
                String lemma = record.get(1);
                String forma = record.get(0).toLowerCase();
                if (!map.containsKey(forma)) {
                    map.put(forma, "");
                }
                if (lemma == null) {
                    lemma = "";
                }

                if (include_lemma) {
                    map.put(forma, map.get(forma) + " " + lemma + "+" + feature);
                } else {
                    map.put(forma, map.get(forma) + " " + feature);
                }
            }

            // put imp in last position
            // todo: we can do it better
            for (Map.Entry<String, String> e : map.entrySet()) {
                if (e.getValue().contains("+imp+")) {
                    String newMorph = "";
                    String[] morphItems = e.getValue().split(" ");
                    String imp = "";
                    for (String m : morphItems) {
                        if (m.contains("+imp+")) {
                            imp = m;
                        } else {
                            newMorph += m + " ";
                        }
                    }
                    newMorph += imp;
                    e.setValue(newMorph);
                }
            }

            for (Map.Entry<String, String> e : map.entrySet()) {
                sink.put(e.getKey(), e.getValue());
            }

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

        SortedTableMap<String, String> stmap = sink.create();
        //volume.close();

        System.out.println("done");

    }
}
