package com.d2v.web.service;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import com.d2v.web.task.CalculateDocSimilarityWorker;
import com.d2v.web.task.DocSimilarityMetaConfig;
import com.d2v.web.utils.ConnEncoder;
import com.d2v.web.utils.SimpleDB;
import com.d2v.web.vo.*;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
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.nd4j.linalg.factory.Nd4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.google.gson.Gson;

@Service
public class Doc2vecService {

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

    @Autowired
    Doc2vecDetailService service;

    // DB 연결
    public Boolean initlizeDBSetting(DBConnectVO dbConnectVO){

        log.info("DB Setting Initlizing...");

        SimpleDB dbSet = new SimpleDB("D2VDB", dbConnectVO.getUrl(), dbConnectVO.getPort(), dbConnectVO.getDbName(), dbConnectVO.getUserName(), dbConnectVO.getPassWd(), SimpleDB.JDBC.ORACLE, dbConnectVO.getConnMax());

        log.info("DB Connection Testing...");

        boolean connectionJudge = dbSet.test();

        if (connectionJudge == true) {
            log.info("DB Connection Test Complete...");
            DocSimilarityMetaConfig.simpleDB = dbSet;

            String jUrl = "jdbc:oracle:thin:@" + dbConnectVO.getUrl() + ":" + dbConnectVO.getPort() + ":" + dbConnectVO.getDbName()
                    + "|" + dbConnectVO.getUserName() + "|" + dbConnectVO.getPassWd();
            try {
                DocSimilarityMetaConfig.JURL = ConnEncoder.encrypt(jUrl, "simpleDB*");
            }
            catch (Exception e) {
                DocSimilarityMetaConfig.JURL = "";
            }
            return true;
        } else {
            log.info("DB Connection Fail");
            return false;
        }
    }

    public Boolean initlizeDBSetting(String jUrl){

        log.info("DB Setting Initlizing...");

        SimpleDB dbSet = new SimpleDB("D2VDB", jUrl, SimpleDB.JDBC.ORACLE, 10);

        log.info("DB Connection Testing...");

        boolean connectionJudge = dbSet.test();

        if (connectionJudge == true) {
            log.info("DB Connection Test Complete...");
            DocSimilarityMetaConfig.simpleDB = dbSet;
            return true;
        } else {
            log.info("DB Connection Fail");
            return false;
        }
    }

    public void extractInputFileDoc() throws IOException {

        SimpleDB dbset = DocSimilarityMetaConfig.simpleDB;

        String filePath = "./input/vector.json";
        String jsonStr = "";
        int j;
        try {
            FileReader in = new FileReader(filePath);
            while ((j = in.read()) != -1) {
                char ch = (char) j;
                jsonStr += ch;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Gson gson = new Gson();
        W2vPropVO w2vPropObj = gson.fromJson(jsonStr, W2vPropVO.class);

        String filePathD2v = "./input/d2vTb.json";
        String jsonStrD2v = "";
        int i;
        try {
            FileReader in = new FileReader(filePathD2v);
            while ((i = in.read()) != -1) {
                char ch = (char) i;
                jsonStrD2v += ch;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Gson gsonD2v = new Gson();
        Doc2vecTableVO doc2vecTableVO = gsonD2v.fromJson(jsonStrD2v, Doc2vecTableVO.class);

        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())));
        DocSimilarityMetaConfig.procVer = version;

        String fileNmPostfix = ".txt";
        String inputFilePath = w2vPropObj.getW2vLog().getInputFilePath();
        String inputFileNm = w2vPropObj.getW2vLog().getInputFileName();
        String inputFile = inputFilePath + inputFileNm + "_" + DocSimilarityMetaConfig.procVer + fileNmPostfix;

        // 최종 버전 확인
        try {
            List<Map<String, Object>> prevVersion = dbset.getTableColumns(doc2vecTableVO.getD2vTable(), Arrays.asList(doc2vecTableVO.getD2vColVerLast()), "order by " + doc2vecTableVO.getD2vColVerLast() + " desc");
            DocSimilarityMetaConfig.prevVer = Integer.valueOf(String.valueOf(prevVersion.get(0).get(doc2vecTableVO.getD2vColVerLast())));
        }
        catch(Exception e) {
            DocSimilarityMetaConfig.prevVer = 0;
        }

        String query = "select " + doc2vecTableVO.getItgtDataCol() + " from " + doc2vecTableVO.getItgtTable() + " where " + doc2vecTableVO.getItgtColVerLast() + " = '" + String.valueOf(version) + "'";
        List<Map<String, Object>> resultSet = dbset.getDataFromSQL(query);
        log.info("Loading Complete : {}", resultSet.size());
        Queue<String> blockingRsQueue = new LinkedBlockingQueue<String>();

        Queue<String> forthBlockingRsQueue = new LinkedBlockingQueue<String>();
        resultSet.parallelStream().forEach(rs -> {
            if(rs.get(doc2vecTableVO.getItgtDataCol()) != null) {
                String keyword = rs.get(doc2vecTableVO.getItgtDataCol()).toString();
                forthBlockingRsQueue.add(keyword);
            }
        });
        Object[] rsMorpArray = forthBlockingRsQueue.toArray();
        String[] rsMorpStrArray = Arrays.copyOf(rsMorpArray, rsMorpArray.length, String[].class);
        String rsMorpStr = String.join("\n", rsMorpStrArray);

        log.debug("result set : {}", rsMorpStr);
        File w2vInputFile = new File(inputFile);
        rsMorpStr = rsMorpStr.replaceAll(" ", "_");
        rsMorpStr = rsMorpStr.replaceAll(";", " ");
        rsMorpStr = rsMorpStr.toLowerCase();

        FileUtils.write(w2vInputFile, rsMorpStr, "UTF-8", true);
        log.info("Extract InputFile Complete");
    }

    public String training() throws Exception {

        Nd4j.getMemoryManager().togglePeriodicGc(true);
        Nd4j.getMemoryManager().setAutoGcWindow(50);
        Nd4j.getMemoryManager().setOccasionalGcFrequency(0);

        String filePath = "./input/vector.json";
        String jsonStr = "";
        int j;
        try {
            FileReader in = new FileReader(filePath);
            while ((j = in.read()) != -1) {
                char ch = (char) j;
                jsonStr += ch;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Gson gson = new Gson();
        W2vPropVO w2vPropObj = gson.fromJson(jsonStr, W2vPropVO.class);

        String fileNmPostfix = ".txt";
        String inputFilePath = w2vPropObj.getW2vLog().getInputFilePath();
        String inputFileNm = w2vPropObj.getW2vLog().getInputFileName();
        String outputFilePath = w2vPropObj.getW2vLog().getOutputFilePath();
        String outputFileNm = w2vPropObj.getW2vLog().getOutputFileName();

        String inputFile = inputFilePath + inputFileNm + "_" + DocSimilarityMetaConfig.procVer + fileNmPostfix;
        String outputFile = outputFilePath + outputFileNm + "_" + DocSimilarityMetaConfig.procVer + fileNmPostfix;

		File w2vInputFile = new File(inputFile);

		Word2vecVO w2vVo = w2vPropObj.getWord2vec();
		WordVectors w2v = service.trainningText(w2vVo, w2vInputFile, outputFile);

		return outputFile;
    }

    /**
     * @param settingVO
     * @throws IOException
     * @throws InterruptedException
     *
     * Doc2Vec 시작 미리 만들어둔 output파일(word2vec) 필요
     */
    public void execute(SettingVO settingVO) throws IOException, InterruptedException {

        String filePath = "./input/d2vTb.json";
        String jsonStr = "";
        int j;
        try {
            FileReader in = new FileReader(filePath);
            while ((j = in.read()) != -1) {
                char ch = (char) j;
                jsonStr += ch;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Gson gson = new Gson();
        Doc2vecTableVO doc2vecTableVO = gson.fromJson(jsonStr, Doc2vecTableVO.class);

        SimpleDB dbset = DocSimilarityMetaConfig.simpleDB;
        DocSimilarityMetaConfig.ERROR_MESSAGE = "";

        try {
            if (DocSimilarityMetaConfig.DOC_ID_MAP_CHECK == 0) {
                List<Map<String, Object>> currentVersion = dbset.getTableColumns(doc2vecTableVO.getDataDefTable(), Arrays.asList(doc2vecTableVO.getDataDefVerLast()), "order by " + doc2vecTableVO.getDataDefVerLast() + " desc");
                String version = String.valueOf(currentVersion.get(0).get(doc2vecTableVO.getDataDefVerLast()));
                List<Map<String, Object>> docResultList = dbset.getDataFromSQL(doc2vecTableVO.getGetDocTitleQuery().replace("{version}", version));
                for (Map<String, Object> docResult : docResultList) {
                    DocSimilarityMetaConfig.DOC_ID_MAP.put(String.valueOf(docResult.get(doc2vecTableVO.getDataDefId())), String.valueOf(docResult.get(doc2vecTableVO.getDataDefContent())));
                }
                DocSimilarityMetaConfig.DOC_ID_MAP_CHECK = 1;
            }
        }
        catch (Exception e) {
//            DocSimilarityMetaConfig.ERROR_MESSAGE = "DocId-Title Matching Query Error...";
            DocSimilarityMetaConfig.DOC_ID_MAP_CHECK = 1;
        }

        log.info("User Task Define Analyze...");

        SelectTableVO choiceTable = settingVO.getSelectTableVO();
        DocSettingVO docSet = settingVO.getDocSettingVO();
        String delimiter = "@";

        DocSimilarityMetaConfig.LAYER_SIZE = 100;
        LoadingDocTable loadTable = new LoadingDocTable();

        log.info("User Task Define Analyze Complete...");

        // source(domain) 테이블 데이터 불러오기
        log.info("Loading Source Table Data...");
        loadTable = service.loadingSourceTable(dbset, docSet, choiceTable, loadTable, "", doc2vecTableVO);

        // source(domain) 테이블 데이터 불러오기 오류시 통과
        if(loadTable.getSourceDbResultList().size() == 0) {
            DocSimilarityMetaConfig.ERROR_MESSAGE = "Source Table Data Error...";
            log.info("Source Table Data Error...");
            DocSimilarityMetaConfig.exceptions.add("Source Table Data Error");
            DocSimilarityMetaConfig.CODE = 400;
            DocSimilarityMetaConfig.state = "ERROR";
            return;
        }

        // target 테이블 데이터 불러오기
        log.info("Loading Target Table Data...");
        loadTable = service.loadingTargetTable(dbset, docSet, choiceTable, loadTable, "", doc2vecTableVO);

        // target 테이블 데이터 불러오기 오류시 통과
        if(loadTable.getTargetDbResultList().size() == 0) {
            DocSimilarityMetaConfig.ERROR_MESSAGE = "Target Table Data Error...";
            DocSimilarityMetaConfig.exceptions.add("Target Table Data Error");
            log.info("Target Table Data Error...");
            DocSimilarityMetaConfig.CODE = 400;
            DocSimilarityMetaConfig.state = "ERROR";
            return;
        }

        Map<Integer, DocKeywordMapVO> mappingSourceDocKeyword = new LinkedHashMap<Integer, DocKeywordMapVO>();
        //+ 문서번호 - 키워드맵핑
        Map<Integer, DocKeywordListMapVO> mappingSourceDocKeywordList = new LinkedHashMap<Integer, DocKeywordListMapVO>();
        Map<Integer, DocKeywordMapVO> mappingTargetDocKeyword = new LinkedHashMap<Integer, DocKeywordMapVO>();
        //+ 문서번호 - 키워드맵핑
        Map<Integer, DocKeywordListMapVO> mappingTargetDocKeywordList = new LinkedHashMap<Integer, DocKeywordListMapVO>();

        Map<String,String> wordMap = new HashMap<>();

        // 불러온 source, target 테이블 키워드를 맵으로 만듦
        log.info("Doc2vec Keyword Input...");
        try {
            mappingSourceDocKeyword = service.extractingSourceDataKeyword(loadTable, docSet, mappingSourceDocKeyword);
            mappingTargetDocKeyword = service.extractingTargetDataKeyword(loadTable, docSet, mappingTargetDocKeyword);
            //기준 문서 키워드 리스트
            mappingSourceDocKeywordList = service.extractSourceDocKeywordSpilt(mappingSourceDocKeyword,
                    mappingSourceDocKeywordList, delimiter, wordMap);
            DocSimilarityMetaConfig.TARGET_KEYWORD_LIST = service.extractTargetDocKeywordSpilt(mappingTargetDocKeyword,
                    mappingTargetDocKeywordList, delimiter, wordMap);
        }
        catch (Exception e) {
            DocSimilarityMetaConfig.ERROR_MESSAGE = "Doc2vec Keyword Input Error...";
            DocSimilarityMetaConfig.exceptions.add("Doc2vec Keyword Input Error");
            log.info("Doc2vec Keyword Input Error...");
            DocSimilarityMetaConfig.CODE = 400;
            DocSimilarityMetaConfig.state = "ERROR";
            return;
        }

        // 키워드 개수
        log.info("Doc2vec Keyword Input Complete...wordMapSize:"+wordMap.size());

        String outputFile = "";
        // w2v 파일 불러오기

        WordVectors w2v = service.trainningText();;

        // w2v 파일 불러오기 오류
        if(w2v == null) {
            DocSimilarityMetaConfig.ERROR_MESSAGE = "Vector Error...";
            DocSimilarityMetaConfig.exceptions.add("Vector Error");
            log.info("Vector Error...");
            DocSimilarityMetaConfig.CODE = 400;
            DocSimilarityMetaConfig.state = "ERROR";
            return;
        }

        DocSimilarityMetaConfig.VOCAB = w2v.vocab();
        DocSimilarityMetaConfig.LOOKUP_TABLE = w2v.lookupTable();

        // 스레드풀 생성
        ExecutorService executorService = Executors.newFixedThreadPool(40);

        // w2v 파일 키워드 개수
        log.info("vocab's size : {" + w2v.vocab().words().size() + "}");

        // target 테이블 문서 벡터 계산
        log.info("Doc2vec Target Vector Build Start...");
        DocSimilarityMetaConfig.TARGET_DOC_VECTOR = service.$tempTargetDocSave(DocSimilarityMetaConfig.TARGET_KEYWORD_LIST,
                DocSimilarityMetaConfig.VOCAB,
                DocSimilarityMetaConfig.LOOKUP_TABLE,
                DocSimilarityMetaConfig.LAYER_SIZE);
        log.info("Doc2vec Target Vector Build End...");

        List<Integer> sourceDocHashs = new ArrayList<>(mappingSourceDocKeywordList.keySet());

        DocSimilarityMetaConfig.SOURCE_DATA_SIZE = sourceDocHashs.size();
        log.info("Doc2vec Calculate Start...");
        //+ 수행해야할 대상을 기준으로
        int endCount = (sourceDocHashs.size()/1000) +1;

        for(int i=0; i< endCount; i++)
        {
            int endpoint = (i+1)*1000;
            if(endpoint>sourceDocHashs.size()){
                endpoint = sourceDocHashs.size();
            }
            List<Integer> workDocHashs =  sourceDocHashs.subList(i*1000, endpoint);
            // Doc2Vec 연산 시작
            CalculateDocSimilarityWorker worker = new CalculateDocSimilarityWorker(mappingSourceDocKeywordList,workDocHashs,dbset,settingVO.getSaveTableNm(), choiceTable, doc2vecTableVO);

            executorService.execute(worker);
        }

        // 스레드 다 끝나면 종료
        executorService.shutdown();
        do {
            System.out.println("Running...");
            if(executorService.isTerminated()) {
                System.out.println("Running2...");
                executorService.shutdownNow();
            }
        } while (!executorService.awaitTermination(300, TimeUnit.SECONDS));

        log.info("Doc2vec Calculate Complete...");
    }
}
