/Users/washida/Project/Robot/MakeKnowledge/MakeProlog/src/makeprolog/Prolog.java
/*
データ、情報をprologの宣言文に変換する
2019年1月27日
 */
package makeprolog;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * ファイルは1行に1文章ずつ入っていると想定する
 * @author washida
 */
public final class Prolog {
    // 当面、小規模なデータであることを想定して、一旦リストに入れる
    List<String> dataLines;
    // 構文解析メイン関数
    Cabocha cabocha;
    // 構文解析パーサー
    Parser parser;
    // 代表的助詞のリスト:ルートを選定するために用いる
    List<String> particles;
    // ノードを作成するための必要な情報を入れフレーズオブジェクトのリスト
    List<Phrase> phrases; 
    // ツリーのどちらからきたか
    int NON = 0; // どちらから来たわけでもない
    int LEFT = 1; // 左から来た
    int RIGHT = 2; // 右から来た
    //
    //芸人とは、なんらかの技芸や芸能の道に通じている人、または身に備わった技芸や芸能をもって職業とする人のことを指す日本特有の概念である
    // フレーズ構造体
    class Phrase{
        String connective = "";  // 副詞か助動詞
        String conjunction = ""; // 接続詞
        String nounverb = ""; // 名詞か動詞、もしなければ・・・・・・・・
        String original = ""; // 原型
    }

    Prolog(){
        dataLines = new ArrayList();
        cabocha = new Cabocha();
        this.parser = cabocha.parser;
        particles = new ArrayList<>();
        setPParticle();
        phrases = new ArrayList<>();
    }
    
    /**
     * prolog化に必要なフレーズの情報をセットする
     * cabochaの句をPhraseクラスに変換する
     * 形容詞など元の句は無くなるので一対一に対応はしない
     */
    void setPhrases(){
        phrases.clear();
        // 一つの後の前に複数の形容詞がつくかもしれないので、リストにする
        List<String> adjectives = new ArrayList<>();
        for(int i=0;i<parser.size();i++){
            List<Part> parts = parser.getPartsOFPhraseNo(i);
            boolean adex = false;
            // フレーズをインスタンス化する
            // 使わないフレーズも発生する
            Phrase ph = new Phrase();
            for (Part part : parts) {
                switch (part.part1) {
                    // フレーズの中の語を調べる
                    case "名詞":
                        ph.nounverb += part.surface;
                        break;
                    case "動詞":
                        if(part.part2.equals("非自立")){
                            // 助詞、助動詞と同じ扱いにして繋げる
                            ph.connective += part.surface;
                        }else{
                            // 原型がある場合は、全体をリスト化する必要がある
                            ph.nounverb += part.surface;
                            ph.original = part.base;
                        }
                        break;
                    case "助詞":
                    case "助動詞":
                        // 次の名詞などと一体化するので、他の情報があったとしても無視される
                        ph.connective += part.surface;
                        break;
                    case "形容詞":
                    case "連体詞":
                        adjectives.add(part.surface);
                        adex = true;
                        break;
                    case "接続詞":
                        ph.conjunction = part.surface;
                        break;
                    default:
                        System.out.println("組み込めなかった語 = " + part.surface + "" + part.part1 + "");
                }
                if(adex){
                    // 形容詞があった場合、他は調べない
                    break;
                }
            }
            if(adex){
                continue;
            }
            if(!ph.nounverb.isEmpty() && ph.connective.isEmpty() && ph.conjunction.isEmpty()){
                //名詞動詞はあって、助動詞、助詞、接続詞がない場合には、擬似助詞をつけておく
                ph.connective = "[ところの,という]";
            }
            if(!adjectives.isEmpty()){
                // 後の前に形容詞・連体詞がある場合は、リスト化する
                String list = "[[";
                for(int j=0;j<adjectives.size();j++){
                    String adj = adjectives.get(j);
                    if(j != 0) list +=", ";
                    list += adj;
                }
                // セットし直す
                // リストは今後さらに拡張し、基本 [[],[],[]]の3項にしたい
                ph.nounverb = list + "], " + ph.nounverb +"]";
            }
            phrases.add(ph);
            // 形容詞は、すぐ後の名詞しか就職しないので、リストはいったん空にする
            adjectives.clear();
        }
        printPhrases();
    }
    
    /**
     * フレーズリストの表示
     */
    void printPhrases(){
        for(Phrase phrase:phrases){
            System.out.println(phrase.connective+",\t"+phrase.nounverb+",\t"+phrase.conjunction+",\t"+phrase.original);
        }
    }
    
    /**
     * lineに与えられた文章をprolog化するメイン関数
     * @param line 
     */
    void mkprolog(String line){
        cabocha.execCabocha(line);
        setPhrases();
        int rootno = getRoot();
        if(rootno < 0){
            System.out.println("ルートフレーズを決定できません = "+line);
            return;
        }
        String clauses = "";
        // 開始ノードかどうかのチェックにも使うので、基本0にすべき
        // この値でインデントをコントロールするのは誤り
        int depth = 0; 
        clauses = makeNode(rootno, clauses, depth, NON);
        System.out.println("%%--------------------------------");
        System.out.println("%% 「"+line+"」のprolog化");
        System.out.println("%% ルート句: No."+rootno
                +" 助詞/動詞:"+phrases.get(rootno).connective
                +" 語:"+phrases.get(rootno).nounverb
                +" 接続詞:"+phrases.get(rootno).conjunction
                +" 原型:"+phrases.get(rootno).original);
        System.out.println("%%--------------------------------");
        System.out.println("pl0000(");
        System.out.print(clauses);
        System.out.println(").");
        System.out.println();
    }
    
    /**
     * prolog化されたテキスト作成メソッド
     * 再帰的に自分自身を呼び出す
     * 
     * @param phraseNo 対象フレーズ番号
     * @param clauses 作成したprolog宣言文
     * @param depth 再帰の深さ主にインデント用に用いられる
     * @param from ツリーを右から来たのか左からきたのかあるいはルートフレーズ化を示す
     * @return 作成したprolog宣言文を返す
     */
    String makeNode(int phraseNo, String clauses, int depth, int from){
        String tab = "    "; // 文章名記述子のために4空白セットする
        for(int i=0;i<depth*4;i++) tab += " ";
        Phrase phrase = phrases.get(phraseNo);
        if(phraseNo == 0){
            // 第1の終了条件、冒頭にいる場合
            if(from != NON){
                // 右から来たことを示している
                // 右が並立か識別子でない限り、名詞ないしは動詞を持っている
                if (!phrase.connective.isEmpty()) {
                    // 助詞、または助動詞が存在するならば
                    clauses += tab + "node(" + phrase.connective + ",\n";
                    // まず、自分の句の中の語を書く
                    clauses += tab + "    " + phrase.nounverb + ",\n";
                    // 一つ右のフレーズの単語を取ってくる
                    // 玉突き的にずらさなければならない
                    clauses += tab + "    " + phrases.get(1).nounverb + "\n";
                    //clauses += tab+"[]\n";
                    clauses += tab+"),\n";
                } else if (!phrase.conjunction.isEmpty()) {
                    // 接続詞は、もっと特別な扱いが必要なのだが、当面、簡単化のために、助詞などと同じ扱いにしておく
                    clauses += tab + "node(" + phrase.conjunction + ",\n";
                    clauses += tab + "    " + phrase.nounverb + ",\n";
                    clauses += tab + "    " + phrases.get(1).nounverb + "\n";
                    clauses += tab+"),\n";
                } else {
                    System.out.println("このフレーズには、助詞、助動詞、接続詞のいずれもがありませんでした");
                    return clauses;
                }
            }else{
                // 冒頭のフレーズがルートになっている場合
                if (!phrase.connective.isEmpty()) {
                    clauses += tab + "node(" + phrase.connective + ",\n";
                    // 句の中の語を書くのは上記と同じ
                    clauses += tab + "    " + phrase.nounverb + ",\n";
                    // 右リーフの対応
                    if(phrases.size() > 0){
                        // 右がある場合、右側を再帰的に呼び出す
                        // makeNodeからの返しを+=してはならない
                        // 右には、左から来た子をと伝える
                        clauses = makeNode(1,clauses,1,LEFT);
                    }else{
                        // 右がない場合は、ここで閉じる
                        clauses += tab + "    " + phrases.get(1).nounverb + "\n";
                    }
                    clauses += tab+")\n";
                } else if (!phrase.conjunction.isEmpty()) {
                    clauses += tab + "node(" + phrase.conjunction + ",\n";
                    clauses += tab + "    " + phrase.nounverb + ",\n";
                    if(phrases.size() > 0){
                        // makeNodeからの返しを+=してはならない
                        clauses = makeNode(1,clauses,1,LEFT);
                    }else{
                        clauses += tab + "    " + phrases.get(1).nounverb + "\n";
                    }
                    clauses += tab+")\n";
                } else {
                    System.out.println("このフレーズには、助詞、助動詞、接続詞のいずれもがありませんでした");
                    return clauses;
                }
            }
        }else if(phraseNo == phrases.size()-1){
            // 第2の終了条件、末尾にいる場合
            // 単純に、そのフレーズの語全体を、右の葉っぱとして加える以外ないと思う
            // ひとつ前の句の表記に最終句の文字を加える
            if(phrase.connective.isEmpty()){
                // 最終フレーズ助詞助動詞がない場合
                // [ところの,という]などという擬似助詞が入ってしまっているのでここは無くなった
                String str = parser.makePhraseString(phraseNo);
                clauses += tab + str + "\n";
            }else{
                clauses += tab + "node(" + phrase.connective + ",\n";
                clauses += tab + "    " + phrase.nounverb + ",\n";
                // 右の葉を空リストにする
                clauses += tab+"[]\n";
                clauses += tab+")\n";
            }
        }else{
            // 最初でも最後でもない場合、つまり前も後ろもある場合
            if(!phrase.connective.isEmpty()){
                clauses += tab + "node(" + phrase.connective + ",\n";
            }else if(!phrase.conjunction.isEmpty()){
                // 当面接続詞は単純化する
                clauses += tab + "node(" + phrase.conjunction + ",\n";
            }else{
                // 事実上、エラー
                clauses += tab + "node([NA],\n";
            }
            // makeNodeからの返しを+=してはならない
            if(from == NON){
                clauses = makeNode(phraseNo-1,clauses,depth+1,RIGHT);
                clauses = makeNode(phraseNo+1,clauses,depth+1,LEFT);
                clauses += tab+")\n";
            }else if(from == RIGHT || from == NON){
                // 右から来た場合
                clauses = makeNode(phraseNo-1,clauses,depth+1,RIGHT);
                // 右の葉には、もう一つ右の言葉を垂らす
                clauses += tab + "    " + phrases.get(phraseNo+1).nounverb + "\n";
                //clauses += tab + "    " + phrase.nounverb + "\n";
                clauses += tab+"),\n";
            }else{
                // 左から来た場合
                clauses += tab + "    " + phrase.nounverb + ",\n";
                clauses = makeNode(phraseNo+1,clauses,depth+1,LEFT);
                clauses += tab+")\n";
            }
        }
        return clauses;
    }
    
    /**
     * ルートノード番号句番号を取得する
     * @return 
     */
    int getRoot(){
        int priority = 9000; // 優先度を大きな数にしておく
        int phraseno = -1;
        for(int i=0;i<phrases.size();i++){
            Phrase phrase = phrases.get(i);
            if (!phrase.connective.isEmpty()) {
                int idx = particles.indexOf(phrase.connective);
                if (idx >= 0) {
                    if (idx < priority) {
                        priority = idx;
                        phraseno = i;
                    }
                }
            }
        }
        return phraseno;
    }
    
    /**
     * ファイルからテキストを読み込む使っていない
     * @param path 
     */
    void readData(String path){
        File file = new File(path);
        System.out.println("データを読み込みます = "+file.getName());
        try {
            BufferedReader br = new BufferedReader(new FileReader(file));
            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty()) {
                    continue;
                }
                dataLines.add(line);
            }
            br.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Prolog.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Prolog.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("データを読み取り終えました = "+dataLines.size());
    }
    
    /**
     * 助詞リストを作成する
     */
    void setPParticle(){
        String [] parts = pparticle.split(",");
        for(String part:parts){
            particles.add(part.trim());
        }
    }
    
    // wikipedia の頻出助詞、助動詞の上位からとってきた
    // http://www.ibot.co.jp/wpibot/?p=2121
    // ただし、本来「の」が圧倒的に頻度が高いのだが、あえて最後にして、他になかった場合のみ選択するようにしている
    // お、に、が、は、は最重要助詞である
    // 何れにしても、この頻度の順に調べて、文章中に最初にあった助詞をルート助詞と設定する
    final String pparticle = "を, に, が, は, と, で, た, な, から, や, も, て, である, として, ている, には, では, ていた, によって, であり, により, であった, による, へ, でも, との, という, まで, への, にも, ない, での, たが, だった, など, ており, だ, ず, などの, より, とは, において, たと, からの, における, について, ば, たり, に対して, なかった, はと, としては, か, に対する, であると, としての, うと, とも, とともに, ていたが, だが, などを, までの, ながら, と共に, へと, に関する, だと, よりも, であるが, などが, ではなく, については, であったが, ているが, にて, てきた, でいる, からは, ても, ていない, などで, に対し, といった, だったが, をと, ていく, などに, ていると, てしまう, までに, にと, においては, でいた, ずに, のみ, なく, たものの, にかけて, の";
    
    /**
     * args に読み込みファイルを指定する
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        //
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        Prolog prolog = new Prolog();
        // JFrameを利用している、不可欠ではない
        Main main = new Main(prolog);
        main.setLocation(500,300);
        main.setVisible(true);
    }
    
}