package org.diagbot.nlp.relation;

import org.algorithm.core.cnn.AlgorithmCNNExecutor;
import org.algorithm.core.cnn.entity.Lemma;
import org.algorithm.core.cnn.entity.Triad;
import org.algorithm.core.cnn.model.impl.RelationExtractionModelImpl;
import org.diagbot.nlp.feature.FeatureType;
import org.diagbot.nlp.participle.ParticipleUtil;
import org.diagbot.nlp.participle.word.Lexeme;
import org.diagbot.nlp.participle.word.LexemePath;
import org.diagbot.nlp.relation.extract.PresentExtract;
import org.diagbot.nlp.relation.extract.VitalExtract;
import org.diagbot.nlp.relation.extract.output.OutputInfo;
import org.diagbot.nlp.util.Constants;
import org.diagbot.nlp.util.NegativeEnum;
import org.diagbot.nlp.util.NlpUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName org.diagbot.nlp.relation.RelationAnalyze
 * @Description 关系抽取入口
 * @Author fyeman
 * @Date 2019/1/18/018 14:36
 * @Version 1.0
 **/
public class RelationAnalyze {
    public List<OutputInfo> analyze(String content, FeatureType featureType) throws Exception {
        String[] part_contents = content.split("\\。|\\；\\\r|\\\n|\\;");

        List<OutputInfo> outputInfos = new ArrayList<>();
        for (String part_content : part_contents) {
            LexemePath<Lexeme> lexemes = ParticipleUtil.participle(part_content, true);
            //分词结果转词元结构，只提取有词性信息，因为部分特征信息未在三元组中，所以需要把分词结果也一并传入
            List<Lemma> lemmaParticiple = lexemeToTriadLemma(lexemes);
            //调用CNN模型
            AlgorithmCNNExecutor executor = new RelationExtractionModelImpl();
            List<Triad> triads = executor.execute(part_content, lemmaParticiple);
            //模型返回的三元组转树形结构
            List<Lemma> lemmaTree = traidToTree(triads, featureType);
            OutputInfo outputInfo = new OutputInfo();
            switch (featureType) {
                case SYMPTOM:
                    PresentExtract presentExtract = new PresentExtract();
                    outputInfo = presentExtract.extract(outputInfo, lemmaTree, lemmaParticiple);
                    //如果还有体征信息 也需要抽取
                    VitalExtract vitalExtract = new VitalExtract();
                    outputInfo = vitalExtract.extract(outputInfo, lemmaTree, lemmaParticiple);
            }
            if (outputInfo != null) {
                outputInfos.add(outputInfo);
            }
        }
        return outputInfos;
    }

    /**
     * 分词结果转词元结构
     * @param lexemes
     * @return
     */
    private List<Lemma> lexemeToTriadLemma(LexemePath<Lexeme> lexemes) {
        List<Lemma> lemmas = new ArrayList<>();
        for (Lexeme lexeme : lexemes) {
            if (!lexeme.getProperty().equals("99")) {
                Lemma lemma = new Lemma();
                lemma.setLen(lexeme.getLength());
                lemma.setPosition(lexeme.getOffset() + "," + (lexeme.getOffset() + lexeme.getLength() - 1));
                lemma.setText(lexeme.getText());
                lemma.setProperty(lexeme.getProperty());
                lemmas.add(lemma);
            }
        }
        return lemmas;
    }

    /**
     * 树形结构最多3层
     * @param triads
     * @param featureType
     * @return
     */
    private List<Lemma> traidToTree(List<Triad> triads, FeatureType featureType) {
        List<Lemma> lemmaTree = new ArrayList<>();
        switch (featureType) {
            case SYMPTOM:
                select(lemmaTree, triads, Constants.symptom_type);
                select(lemmaTree, triads, Constants.vital_type);
        }
        return lemmaTree;
    }

    private void select(List<Lemma> lemmaTree, List<Triad> triads, NegativeEnum[] type) {
        List<Triad> has_add_triads = new ArrayList<>();
        for (Triad triad : triads) {
            Lemma origin_l = null;
            Lemma relation_l = null;
            if (NlpUtil.isFeature(triad.getL_1().getProperty(), type)) {
                origin_l = this.copyProperties(triad.getL_1());
                relation_l = this.copyProperties(triad.getL_2());
            }
            if (NlpUtil.isFeature(triad.getL_2().getProperty(), type)) {
                origin_l = this.copyProperties(triad.getL_2());
                relation_l = this.copyProperties(triad.getL_1());
            }
            if (origin_l != null && relation_l != null) {
                boolean isFindFromTree = false;
                for (Lemma l : lemmaTree) {
                    if (l.getText().equals(origin_l.getText()) && l.getPosition().equals(origin_l.getPosition())) {
                        origin_l = l;
                        isFindFromTree = true;
                        break;
                    }
                }
                this.findRelationTriad(triads, triad, has_add_triads, relation_l, type);
                origin_l.add(relation_l);
                if (!isFindFromTree) {      //树上已有节点 不需要添加
                    lemmaTree.add(origin_l);
                }
                //已添加到树中的三元组
                has_add_triads.add(triad);
            }
        }
        triads.removeAll(has_add_triads);
    }

    private boolean findRelationTriad(List<Triad> triads, Triad current_triad, List<Triad> has_add_triads, Lemma lemma, NegativeEnum[] type) {
        boolean isFindRelation = false;
        for (Triad triad : triads) {
            if (current_triad == triad) {
                continue;
            }
            if (triad.getL_1().getText().equals(lemma.getText())
                    && triad.getL_1().getPosition().equals(lemma.getPosition())
                    && !NlpUtil.isFeature(triad.getL_2().getProperty(), type)) {
                lemma.add(this.copyProperties(triad.getL_2()));
                isFindRelation = true;
                //已添加到树中的三元组
                has_add_triads.add(triad);
            }
            if (triad.getL_2().getText().equals(lemma.getText())
                    && triad.getL_2().getPosition().equals(lemma.getPosition())
                    && !NlpUtil.isFeature(triad.getL_1().getProperty(), type)) {
                lemma.add(this.copyProperties(triad.getL_1()));
                isFindRelation = true;
                //已添加到树中的三元组
                has_add_triads.add(triad);
            }
        }
        return isFindRelation;
    }

    private Lemma copyProperties(Lemma lemma) {
        Lemma l = new Lemma();
        l.setLen(lemma.getLen());
        l.setPosition(lemma.getPosition());
        l.setProperty(lemma.getProperty());
        l.setText(lemma.getText());
        return l;
    }
}
