/*
Opis:
Narzędzie służące do generowania dwóch list zawierających informacje o synsetach
oraz przypisanych do nich pojęciach sumo w korpusie tekstu.
Pierwsza lista zawiera znaczenia, które nie zostały zrzutowane, natomiast druga, 
te które zostały zrzutowane na sumo. Narzędzie generuje listy bazując na 
korpusie tekstu, który zawiera tagi WSD przypisane ręcznie oraz tagi pojęć 
sumo przypisane w oparciu o tagi ręczne.

Uwaga: W przypadku, gdy ręcznnie przypisany synset powtarza się w korpusie tekstu, 
       to informacja o nim w pliku wynikowym będzie zawarta tylko jeden raz.

Format listy zawierającej znaczenia, które nie zostały zrzutowane na sumo:
synset_str_plwn;dziedzina_plwn;synset_id_plwn

Przykład:
Europa Wschodnia.1;msc-12;35113

Format listy zawierającej znaczenia, które zostały zrzutowane na sumo:
synset_str_plwn;dziedzina_plwn;synset_id_plwn;rel_miedzyjezykowa;synset_str_pwn; \
pwn_synset_pos;dziedzina_pwn;synset_id_pwn;pojecie_sumo;rel_sumo_pwn;rel_sumo_pwn_skrot; \
plwn_sumo_rel;plwn_sumo_rel_step1;plwn_sumo_rel_step2;plwn_sumo_rel_step3

Przykład:
Europa Wschodnia.1;msc-12;35113;mero:cz_pa;Europe.1; \
n;rz-14;334702;Europe;equivalent;=; \
instance_of;instance_of;instance_of;instance_of


Przykład uruchomienia:
./lists \
    ~/Korpusy/kpwr-1.1/index_wsd.txt \
    ~/Korpusy/kpwr-1.1/ \
    ~/Sources/plwn_pwn_sumo.csv \
    ~/Sources/list_without_sumo.csv \
    ~/Sources/list_with_sumo.csv

1 parametr: ścieżka do pliku ze spisem wszystkich plików tekstu w korpusie
2 parametr: ścieżka do katalogu z korpusem tekstu
3 parametr: ścieżka do pliku z synsetami zmapowanymi na pojęcia sumo
4 parametr: ścieżka do pliku wynikowego, który będzie zawierał unikalne znaczenia,
            które nie zostały zrzutowane na sumo
5 parametr: ścieżka do pliku wynikowego, który będzie zawierał unikalne znaczenia,
            które zostały zrzutowane na sumo
*/


#include <iostream>
#include <fstream>

#include <string>

#include <libcorpus2/tagsetmanager.h>
#include <libcorpus2/io/xcesreader.h>
#include <libcorpus2/tagging.h>
#include <libcorpus2/io/cclwriter.h>
#include <libcorpus2/sentence.h>

#include <mysql/mysql.h>

#include <map>
#include <vector>

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/classification.hpp>

using namespace std;


void show_usage()
{
    cout << "Uzycie: \n";
    cout << "./lists INPUT_FILE INPUT_DIR INPUT_SUMO_FILE ";
    cout << "OUTPUT_LIST_WITHOUT_SUMO OUTPUT_LIST_WITH_SUMO\n\n";

    cout << "Przykład użycia: \n";
    cout << "./lists \\ \n";
    cout << "    ~/Korpusy/kpwr-1.1/index_wsd.txt \\ \n";
    cout << "    ~/Korpusy/kpwr-1.1/ \\ \n";
    cout << "    ~/Sources/plwn_pwn_sumo.csv \\ \n";
    cout << "    ~/Sources/list_without_sumo.csv \\ \n";
    cout << "    ~/Sources/list_with_sumo.csv \n\n";

    cout << "Error: Wszystkie argumenty sa wymagane. \n";
}


map<int, string> make_sense_map()
{
    map<int, string> senseMap;

    MYSQL *connection;
    MYSQL_RES *result;
    MYSQL_ROW row;

    connection = mysql_init(NULL);

    mysql_options(connection, MYSQL_SET_CHARSET_NAME, "utf8");
    mysql_options(connection, MYSQL_INIT_COMMAND, "SET NAMES utf8");

    mysql_real_connect(connection, "156.17.10.92", "wordnet", "VfazYsAc9ER8Qjrr",
        "wordnet_work", 3306, NULL, 0);

    /*
    Wynik kwerendy:
    +--------+--------------------+--------+--------+-------------------------------------------------------------+
    | LEX_ID | lemma              | domain | SYN_ID | unitsstr                                                    |
    +--------+--------------------+--------+--------+-------------------------------------------------------------+
    |    ... | ...                |    ... |    ... | ...                                                         |
    | 612209 | żwawik czerwieniak |     21 | 384935 | (żwawik czerwieniak 1* (zw) [zw] | żwawik czerwony 1* (zw)) |
    | 612210 | żwawik czerwony    |     21 | 384935 | (żwawik czerwieniak 1* (zw) [zw] | żwawik czerwony 1* (zw)) |
    |    ... | ...                |    ... |    ... | ...                                                         |
    +--------+--------------------+--------+--------+-------------------------------------------------------------+
    */
    mysql_query(connection,
        "SELECT LEX.ID, LEX.lemma, LEX.domain, SYN.ID, SYN.unitsstr " \
        "FROM wordnet_work.synset SYN " \
        "JOIN wordnet_work.unitandsynset UAS ON (SYN.ID = UAS.SYN_ID) " \
        "JOIN wordnet_work.lexicalunit LEX ON (UAS.LEX_ID = LEX.ID);");
    result = mysql_store_result(connection);

    while(row = mysql_fetch_row(result)) {
        //rozdzielenie wiersza unitsstr na:
            //"(żwawik czerwieniak 1* (zw) [zw] " oraz " żwawik czerwony 1* (zw))"
        vector<string> spl_row4;
        boost::split(spl_row4, row[4], boost::algorithm::is_any_of("|"));

        //rozdzielenie nazwy synsetu od jego dziedziny na:
            //"żwawik czerwieniak 1* ", "zw" oraz  "[zw] "
        vector<string> spl_syn_string;
        boost::split(spl_syn_string, spl_row4[0], 
            boost::algorithm::is_any_of("()"));
        //usunięcie spacji z końca "żwawik czerwieniak 1* "
        string synset_str_plwn = 
            spl_syn_string[1].substr(0, spl_syn_string[1].size()-1);
        //usunięcie jeśli jest "*" z końca "żwawik czerwieniak 1*"
        boost::replace_all(synset_str_plwn, "*", "");

        //rozdzielenie nazwy synsetu na części oraz jego numeru na:
            //"żwawik", "czerwieniak", "1"
        vector<string> spl_synset_str_plwn;
        boost::split(spl_synset_str_plwn, synset_str_plwn, 
            boost::algorithm::is_any_of(" "));
        //połączenie rozdzielonej nazwy synsetu oraz jego numeru uzyskując:
            //synset_str_plwn = "żwawik czerwieniak.1"
        synset_str_plwn = "";
        for(int i=0; i<spl_synset_str_plwn.size()-1; i++) {
            synset_str_plwn += spl_synset_str_plwn[i];
            if(i != spl_synset_str_plwn.size()-2)
                synset_str_plwn += " ";
        }
        synset_str_plwn += "." + spl_synset_str_plwn.back();

        //połączenie dziedziny synsetu z numerem domain uzyskując:
            //dziedzina_plwn = "zw-21"
        string dziedzina_plwn = spl_syn_string[2] + "-" + row[2];

        //zapisanie w mapie danych dotyczących synsetu w postaci:
            //[lemma] -> [synset_str_plwn;dziedzina_plwn;synset_id]
            //[żwawik czerwieniak] -> [żwawik czerwieniak.1;zw-21;384935]
        string synset_description = 
            synset_str_plwn + ";" + dziedzina_plwn + ";" + row[3];

        int synsetID = atoi(row[3]);
        senseMap[synsetID] = synset_description;
    }

    return senseMap;
}

map<string, vector<string> > make_annotation_map()
{
    map<string, vector<string> > annMap;

    MYSQL *connection;
    MYSQL_RES *result;
    MYSQL_ROW row;

    connection = mysql_init(NULL);

    mysql_options(connection, MYSQL_SET_CHARSET_NAME, "utf8");
    mysql_options(connection, MYSQL_INIT_COMMAND, "SET NAMES utf8");

    mysql_real_connect(connection, "156.17.10.92", "wordnet", "VfazYsAc9ER8Qjrr",
        "wordnet_work", 3306, NULL, 0);

    mysql_query(connection,
        "SELECT wordnet_work.synset.comment, wordnet_work.synset.ID " \
        "FROM wordnet_work.synset " \
        "WHERE wordnet_work.synset.comment LIKE '%wsd%';");
    result = mysql_store_result(connection);


    while(row = mysql_fetch_row(result)) {
        vector<string> spl_key_row0;
        boost::split(spl_key_row0, row[0], boost::algorithm::is_any_of(";"));

        vector<string> spl_key;
        boost::split(spl_key, spl_key_row0[0], boost::algorithm::is_any_of(","));

        for(int i=0; i<spl_key.size(); i++) {
            boost::replace_all(spl_key[i], "WSD", "");
            boost::replace_all(spl_key[i], " ", "");
            boost::replace_all(spl_key[i], "#", "-");

            if(annMap.find(spl_key[i]) != annMap.end()) {
                annMap[spl_key[i]].push_back(row[1]);
            }
            else {
                vector<string> newValue;
                newValue.push_back(row[1]);
                annMap[spl_key[i]] = newValue;
            }
        }
    }

    return annMap;
}


map <int, vector<string> > make_sumo_map(string sumoFileName) {
    map<int, vector<string> > sumoMap;
    sumoMap.clear();

    ifstream fileSumo;
    fileSumo.open(sumoFileName.c_str());
    string line;
    while(getline(fileSumo, line)) {
        vector<string> lineVector;
        boost::split(lineVector, line, boost::algorithm::is_any_of(";"));

    	if(lineVector.back().compare("R") != 0) {
            int synsetID = atoi(lineVector[2].c_str());

            if(sumoMap.find(synsetID) != sumoMap.end()) {
                sumoMap[synsetID].push_back(line);
            }
            else {
                vector<string> newValue;
                newValue.push_back(line);
                sumoMap[synsetID] = newValue;
            }
    	}
    }

    fileSumo.close();

    return sumoMap;
}


int main(int argc, char *argv[])
{
    if(argc < 6) {
        show_usage();
        return -1;
    }



    const Corpus2::Tagset tagset = Corpus2::get_named_tagset("nkjp");



    //stworzenie mapy synsetów
    cout << "Making synset dictionary...\n";
    map<int, string> senseMap;
    senseMap = make_sense_map();

    //stworzenie mapy synsetów i odpowiadających im pojęć sumo
    //na podstawie pliku wejściowego zawierającego pojęcia sumo
    cout << "Making sumo dictionary...\n";
    string sumoFileName = argv[3];
    map <int, vector<string> > sumoMap;
    sumoMap = make_sumo_map(sumoFileName);

    //stworzenie mapy, która mapuje synsety z komentarzy
    cout << "Making annotation dictionary...\n";
    map<string, vector<string> > annMap;
    annMap = make_annotation_map();



    map<int, string> notSumoTagMap;
    map<int, vector<string> > sumoTagMap;



    ifstream fileIndex;
    fileIndex.open(argv[1]);

    string line = "";
    while(getline(fileIndex, line)) {
        string pathToFile = argv[2];
        pathToFile.append(line);

        boost::shared_ptr<Corpus2::TokenReader> reader =
            Corpus2::TokenReader::create_path_reader
            ("ccl,disamb_only", tagset, pathToFile);

        boost::shared_ptr<Corpus2::Chunk> chunk;

        while(chunk = reader->get_next_chunk()) {
            const std::vector<boost::shared_ptr
                <Corpus2::Sentence> > &sentencesInChunk =
                chunk->sentences();

            for(unsigned s=0; s<sentencesInChunk.size(); s++) {
                boost::shared_ptr<Corpus2::Sentence> sentence =
                    sentencesInChunk[s];
                const std::vector<Corpus2::Token *> &tokensInSentence =
                    sentence->tokens();

                for(unsigned tkn=0; tkn<tokensInSentence.size(); tkn++) {
                    Corpus2::Token *token = tokensInSentence[tkn];

                    boost::shared_ptr<Corpus2::TokenMetaData> metaData =
                        token->get_metadata();

                    if(metaData) {
                        const std::map<std::string, std::string> &attrMap =
                            token->get_metadata()->attributes();

                        for(std::map<std::string, std::string>::const_iterator
                            attrIt = attrMap.begin();
                            attrIt != attrMap.end();
                            ++attrIt
                        ) {
                            if(0 == attrIt->first.find("sense:wsd_")) {
                                if(annMap.find(attrIt->second) == annMap.end()) {
                                    cout << "Lack of key ";
                                    cout << attrIt->second;
                                    cout << " in dictionary!\n";
                                    continue;
                                }

                                for(int nr_id=0; 
                                    nr_id<annMap.find(attrIt->second)->second.size(); 
                                    nr_id++
                                ) {
                                    int sense_id = 
                                        atoi(annMap.find(attrIt->second)->second[nr_id].c_str());

                                    string sumo = metaData->get_attribute("sumo_map");
                                    if(sumo.empty()) {
                                        if(notSumoTagMap.find(sense_id) == notSumoTagMap.end()) {
                                            notSumoTagMap[sense_id] = 
                                                senseMap.find(sense_id)->second;
                                        }
                                    }
                                    else {
                                        if(sumoMap.find(sense_id) == sumoMap.end()) {
                                            cout << "Lack of sumo concept!\n";
                                            continue;
                                        }

                                        if(sumoTagMap.find(sense_id) == sumoTagMap.end()) {
                                            for(int i=0; i<sumoMap.find(sense_id)->second.size(); i++)
                                                sumoTagMap[sense_id].
                                                    push_back(sumoMap.find(sense_id)->second[i]);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    fileIndex.close();


    cout << "Writing to file: " << argv[4] << "\n";
    ofstream outFile(argv[4]);
    outFile << "synset_str_plwn;dziedzina_plwn;synset_id_plwn\n";

    std::map<int, string>::const_iterator senseIt;
    for(senseIt = notSumoTagMap.begin(); senseIt != notSumoTagMap.end(); ++senseIt)
        outFile << senseIt->second << "\n";

    outFile.close();


    cout << "Writing to file: " << argv[5] << "\n";
    ofstream outFileSumo(argv[5]);
    outFileSumo << "synset_str_plwn;dziedzina_plwn;synset_id_plwn;";
    outFileSumo << "rel_miedzyjezykowa;synset_str_pwn;pwn_synset_pos;";
    outFileSumo << "dziedzina_pwn;synset_id_pwn;pojecie_sumo;";
    outFileSumo << "rel_sumo_pwn;rel_sumo_pwn_skrot;plwn_sumo_rel;";
    outFileSumo << "plwn_sumo_rel_step1;plwn_sumo_rel_step2;plwn_sumo_rel_step3\n";

    std::map<int, vector<string> >::const_iterator sumoIt;
    for(sumoIt = sumoTagMap.begin(); sumoIt != sumoTagMap.end(); ++sumoIt) {
        for(int i=0; i<sumoIt->second.size(); i++)
            outFileSumo << sumoIt->second[i] << "\n";
    }

    outFileSumo.close();


    cout << "Done!\n";
    return 0;
}

