package com.d2v.web.task;

import com.d2v.web.task.DocSimilarityMetaConfig;
import com.d2v.web.utils.SimpleDB;
import com.d2v.web.vo.*;
import org.apache.commons.collections4.CollectionUtils;
import org.deeplearning4j.models.embeddings.WeightLookupTable;
import org.deeplearning4j.models.embeddings.wordvectors.WordVectors;
import org.deeplearning4j.models.word2vec.VocabWord;
import org.deeplearning4j.models.word2vec.wordstore.VocabCache;
import org.mapdb.Atomic;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.ops.transforms.Transforms;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicInteger;

public class CalculateDocSimilarityWorker  implements Runnable{

    private static final Logger log = LoggerFactory.getLogger(CalculateDocSimilarityWorker.class);

    Map<Integer, DocKeywordListMapVO> docKeywordListMapVO;
    List<Integer> workDocHashs;
    SimpleDB dbset;
    String saveTable;
    SelectTableVO selectTableVO;
    Doc2vecTableVO doc2vecTableVO;

    public CalculateDocSimilarityWorker(
            Map<Integer,DocKeywordListMapVO>  docKeywordListMapVO,
            List<Integer> workDocHashs,
            SimpleDB dbset,
            String saveTable,
            SelectTableVO selectTableVO,
            Doc2vecTableVO doc2vecTableVO
    ) {
        this.workDocHashs = workDocHashs;
        this.docKeywordListMapVO = docKeywordListMapVO;
        this.dbset = dbset;
        this.saveTable = saveTable;
        this.selectTableVO = selectTableVO;
        this.doc2vecTableVO = doc2vecTableVO;
    }

    @Override
    public void run() {
        DocSimilarityMetaConfig.state = "PROC";
        List<Map<String, Object>> currentVersion = dbset.getTableColumns(doc2vecTableVO.getItgtTable(), Arrays.asList(doc2vecTableVO.getItgtColVerLast()), "order by " + doc2vecTableVO.getItgtColVerLast() + " desc");
        int version = Integer.valueOf(String.valueOf(currentVersion.get(0).get(doc2vecTableVO.getItgtColVerLast())));

        VocabCache<VocabWord> vocab = DocSimilarityMetaConfig.VOCAB;

        WeightLookupTable<VocabWord> lookupTable = DocSimilarityMetaConfig.LOOKUP_TABLE;

        Map<Integer, TargetDocVecVO> targetDocVec = DocSimilarityMetaConfig.TARGET_DOC_VECTOR;

        for(Integer sourceDocHash : workDocHashs)
        {
            DocKeywordListMapVO sourceKeywordVO = docKeywordListMapVO.get(sourceDocHash);
            String sourceDocId = String.valueOf(sourceKeywordVO.getDocId());
            String sourceDocSimilarIdStr = "";
            if (CollectionUtils.isNotEmpty(sourceKeywordVO.getDocIdList())) {
                Set<String> sourceDocIdSet = new LinkedHashSet<String>();
                for (int i = 0; i < sourceKeywordVO.getDocIdList().size(); i++) {
                    sourceDocIdSet.add(sourceKeywordVO.getDocIdList().get(i));
                }
                sourceDocSimilarIdStr = sourceDocIdSet.toString();
            }
            String sourceDocNm  = String.valueOf(sourceKeywordVO.getDocName());

            int workingNumber = DocSimilarityMetaConfig.WORKING_COUNT.incrementAndGet();

            long startTimeMillis = System.currentTimeMillis();

            if(workingNumber % 1000 == 0) {
                log.info("(Start) [{}/{}] Source Document : {} / {}", workingNumber, DocSimilarityMetaConfig.SOURCE_DATA_SIZE,
                        sourceDocId, sourceKeywordVO.getDocName());
            }

            List<String> firstDomainKeywordList = sourceKeywordVO.getDocKeywordList();
            INDArray docVector = Nd4j.create(DocSimilarityMetaConfig.LAYER_SIZE);

            // source 문서 벡터 계산
            for (int i = 0; i < firstDomainKeywordList.size(); i++) {
                INDArray array = $extractArrayValue(lookupTable, vocab, firstDomainKeywordList.get(i));
                if (array != null) {
                    docVector = docVector.add(array);
                }
            }

            docVector = docVector.div(firstDomainKeywordList.size());

            List<Map<String,Object>> taskListStorage = new LinkedList<>();
            List<Map<String,Object>> jobListStorage = new LinkedList<>();
            List<Map<String,Object>> trainingListStorage = new LinkedList<>();
            List<Map<String,Object>> licenseListStorage = new LinkedList<>();
            List<Map<String,Object>> majorListStorage = new LinkedList<>();
            List<Map<String,Object>> etcListStorage = new LinkedList<>();

            for (Map.Entry<Integer, TargetDocVecVO> b : targetDocVec.entrySet()) {
                String targetDocId = b.getValue().getDocId();
                String targetDocSimilarIdStr = b.getValue().getDocIdList();
                String targetDocNm = b.getValue().getDocNm();
                INDArray targetDocVector = b.getValue().getDocVector();

                // 문서 유사도 계산
                double similarity = $extractDocSimilarity(docVector, targetDocVector);

                if (Double.isNaN(similarity)) {
                    log.debug("document similarity ({}_{}) = (vectors = {}, divisor = {})", sourceDocId, b.getKey(),
                            similarity);
                    similarity = -2.0d;
                }

                Map<String,Object> data = new HashMap<>();
                String targetDomain = targetDocId.split("-")[0];
                String domain = sourceDocId.split("-")[0] + "-" + targetDocId.split("-")[0];
                String idx = domain + "_" + sourceDocNm + "-" + targetDocNm + "-" + String.valueOf(version);
                // db 컬럼, 컬럼에 넣을 데이터
                data.put(doc2vecTableVO.getD2vTableCol().getMtcgIdx(), idx);
                data.put(doc2vecTableVO.getD2vTableCol().getItgtIdx(), sourceDocId);
                data.put(doc2vecTableVO.getD2vTableCol().getItgtIdxTrgt(), targetDocId);
                data.put(doc2vecTableVO.getD2vTableCol().getVersion(), version);
                data.put(doc2vecTableVO.getD2vTableCol().getDomain(), domain);
                data.put(doc2vecTableVO.getD2vTableCol().getDocId(), sourceDocNm);
                data.put(doc2vecTableVO.getD2vTableCol().getDocIdTrgt(), targetDocNm);
                data.put(doc2vecTableVO.getD2vTableCol().getDocTitle(), DocSimilarityMetaConfig.DOC_ID_MAP.get(sourceDocNm));
                data.put(doc2vecTableVO.getD2vTableCol().getDocTitleTrgt(), DocSimilarityMetaConfig.DOC_ID_MAP.get(targetDocNm));
                data.put(doc2vecTableVO.getD2vTableCol().getMatchScore(), String.valueOf(similarity));

                if(targetDomain.equals("직무")) {
                    taskListStorage.add(data);
                }
                else if(targetDomain.equals("직업")) {
                    jobListStorage.add(data);
                }
                else if(targetDomain.equals("훈련")) {
                    trainingListStorage.add(data);
                }
                else if(targetDomain.equals("자격증")) {
                    licenseListStorage.add(data);
                }
                else if(targetDomain.equals("학과")) {
                    majorListStorage.add(data);
                }
                else {
                    etcListStorage.add(data);
                }
            }
            // 유사도 순 정렬
            taskListStorage = sortListStorage(taskListStorage);
            jobListStorage = sortListStorage(jobListStorage);
            trainingListStorage = sortListStorage(trainingListStorage);
            licenseListStorage = sortListStorage(licenseListStorage);
            majorListStorage = sortListStorage(majorListStorage);
            etcListStorage = sortListStorage(etcListStorage);

            // 상위 100개 자르기
            taskListStorage = subListStorage(taskListStorage);
            jobListStorage = subListStorage(jobListStorage);
            trainingListStorage = subListStorage(trainingListStorage);
            licenseListStorage = subListStorage(licenseListStorage);
            majorListStorage = subListStorage(majorListStorage);
            etcListStorage = subListStorage(etcListStorage);

            // db 저장
            boolean taskCheck = true;
            boolean jobCheck = true;
            boolean trainingCheck = true;
            boolean licenseCheck = true;
            boolean majorCheck = true;
            boolean etcCheck = true;
            if(taskListStorage.size() != 0) {
                taskCheck = dbset.InsertDataList(saveTable, taskListStorage);
            }
            if(jobListStorage.size() != 0) {
                jobCheck = dbset.InsertDataList(saveTable, jobListStorage);
            }
            if(trainingListStorage.size() != 0) {
                trainingCheck = dbset.InsertDataList(saveTable, trainingListStorage);
            }
            if(licenseListStorage.size() != 0) {
                licenseCheck = dbset.InsertDataList(saveTable, licenseListStorage);
            }
            if(majorListStorage.size() != 0) {
                majorCheck = dbset.InsertDataList(saveTable, majorListStorage);
            }
            if(etcListStorage.size() != 0) {
                etcCheck = dbset.InsertDataList(saveTable, etcListStorage);
            }

            // db저장 오류 났으면 통과
            if(!taskCheck || !jobCheck || !trainingCheck || !licenseCheck || !majorCheck || !etcCheck) {
                DocSimilarityMetaConfig.ERROR_MESSAGE = "InsertData Error...";
                log.info("InsertData Error...");
                return;
            }

            if(workingNumber % 1000 == 0) {
                log.info("(End+{} s) [{}/{}] Target Document : {}",
                        String.format("%.3f", (System.currentTimeMillis() - startTimeMillis) * 0.001), workingNumber,
                        DocSimilarityMetaConfig.SOURCE_DATA_SIZE, sourceDocNm);
            }
        }
    }


    private INDArray $extractArrayValue(WeightLookupTable<VocabWord> lookupTable, VocabCache<VocabWord> vocab,
                                        String word) {
        if (vocab.containsWord(word)) {
            INDArray wordArray = lookupTable.vector(word);
            return wordArray;
        } else {
            return null;
        }
    }


    private double $extractDocSimilarity(INDArray sourceDocVec, INDArray targetDocVec) {
        double similarity = 0.0;
        similarity = Math.abs(Transforms.cosineSim(sourceDocVec, targetDocVec));
        return similarity;
    }

    private List<Map<String,Object>> sortListStorage(List<Map<String,Object>> listStorage) {
        Collections.sort(listStorage, new Comparator<Map<String, Object>>() {
            public int compare(Map<String, Object> map1, Map<String, Object> map2) {
                Double simi1 = Double.valueOf(String.valueOf(map1.get("MATCH_SCORE")));
                Double simi2 = Double.valueOf(String.valueOf(map2.get("MATCH_SCORE")));
                return simi2.compareTo(simi1);
            }
        });
        return listStorage;
    }

    private List<Map<String,Object>> subListStorage(List<Map<String,Object>> listStorage) {
        if(listStorage.size() > 100) {
            listStorage = listStorage.subList(0, 100);
        }
        return listStorage;
    }
}
