/* TMeaning.java - SQL operations with the table 'meaning' in Wiktionary
 * parsed database.
 *
 * Copyright (c) 2009-2011 Andrew Krizhanovsky <andrew.krizhanovsky at gmail.com>
 * Distributed under EPL/LGPL/GPL/AL/BSD multi-license.
 */

package wikokit.base.wikt.sql;

//import wikipedia.language.Encodings;
//import wikipedia.sql.PageTableBase;
import wikokit.base.wikipedia.sql.Connect;
import java.sql.*;
import wikokit.base.wikt.constant.Relation;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import wikokit.base.wikt.sql.quote.TQuote;

/** An operations with the table 'meaning' in MySQL wiktionary_parsed database.
 *
 * @see wikt.word.WPOS
 */
public class TMeaning {

    /** Unique identifier in the table 'meaning'. */
    private int id;

    
    /** Link to the table 'lang_pos', which defines language and POS.
     * If lang_pos != null, then lang_pos_id is not used; lazy DB access.
     */
    private TLangPOS lang_pos;          // int lang_pos_id;

    /** Link to the table 'lang_pos', which defines language and POS. 
     * If lang_pos != null, then lang_pos_id is not used.
     */
    private int lang_pos_id;
    

    /** Meaning (sense) number. */
    private int meaning_n;
    
    
    /** Wikified text describing this meaning.
     * If wiki_text != null, then wiki_text_id is not used; lazy DB access.
     */
    private TWikiText wiki_text;        // int wiki_text_id

    /** ID of wikified text in a table 'wiki_text.
     * If wiki_text != null, then wiki_text_id is not used; lazy DB access.
     */
    private int wiki_text_id;

    /** Quotations illustrate the meaning. */
    private TQuote[] quotation;

    /** Semantic relations: synonymy, antonymy, etc.
     * The map from semantic relation (e.g. synonymy) to array of WRelation
     * (one WRelation contains a list of synonyms for one meaning).
     */
    private Map<Relation, TRelation[]> relation;

    /** Translation */
    private TTranslation translation;


    private final static Map<Relation, TRelation[]> NULL_MAP_RELATION_TRELATION_ARRAY = new HashMap<Relation, TRelation[]>();
    private final static TMeaning[] NULL_TMEANING_ARRAY = new TMeaning[0];
    //private final static TRelation[] NULL_TRELATION_ARRAY = new TRelation[0];

    /** Constructor.
     * @param _id
     * @param _lang_pos
     * @param _lang_pos_id
     * @param _meaning_n
     * @param _wiki_text
     * TLangPOS _lang_pos or int _lang_pos_id should be valid,
     * if lang_pos != null, then _lang_pos_id is not used.
     */
    public TMeaning(int _id,TLangPOS _lang_pos,int _lang_pos_id,
                    int _meaning_n,
                    TWikiText _wiki_text,int _wiki_text_id) {
        id          = _id;
        lang_pos    = _lang_pos;
        lang_pos_id = _lang_pos_id;
        meaning_n   = _meaning_n;
        wiki_text   = _wiki_text;
        wiki_text_id= _wiki_text_id;

        relation    = NULL_MAP_RELATION_TRELATION_ARRAY;
    }

    /** Gets unique ID from database */
    public int getID() {
        return id;
    }

    /** Gets meaning (sense) number from database */
    public int getMeaningNumber() {
        return meaning_n;
    }

    /** Gets language and POS ID (for this meaning) from the database' table 'lang_pos'. */
    public TLangPOS getLangPOS(Connect connect) {

        if(null != lang_pos)
            return lang_pos;

        lang_pos = TLangPOS.getByID(connect, lang_pos_id);  // lazy DB access
        return lang_pos;
    }

    /** Gets text object describing this meaning (without wikification) */
    public TWikiText getWikiText() {
        return wiki_text;
    }
    
    /** Gets text describing this meaning (without wikification) or empty String "". */
    public String getWikiTextString() {
        if(null == wiki_text)
            return "";
        return wiki_text.getText();
    }

    /** Gets relation map */
    public Map<Relation, TRelation[]> getRelation() {
        return relation;
    }


    
    /** Inserts record into the table 'meaning'.<br><br>
     * INSERT INTO meaning (lang_pos_id,meaning_n,wiki_text_id) VALUES (1,2,3);
     * @param lang_pos  ID of language and POS of wiki page which will be added
     * @param meaning_n meaning (sense) number
     * @param wiki_text wikified text (definitions), it could be null (since an article can contain synonyms without a definition of meaning)
     * @return inserted record, or null if insertion failed
     */
    public static TMeaning insert (Connect connect,TLangPOS lang_pos,
            int meaning_n, TWikiText wiki_text) {

        if(null == lang_pos) {
            System.out.println("Error (wikt_parsed TMeaning.insert()):: null argument lang_pos");
            return null;
        }

        StringBuilder str_sql = new StringBuilder();
        TMeaning meaning = null;
        int wiki_text_id = 0;
        try
        {
            Statement s = connect.conn.createStatement ();
            try {
                if(null != wiki_text)
                    str_sql.append("INSERT INTO meaning (lang_pos_id,meaning_n,wiki_text_id) VALUES (");
                else
                    str_sql.append("INSERT INTO meaning (lang_pos_id,meaning_n) VALUES (");
                str_sql.append(lang_pos.getID());
                str_sql.append(",");
                str_sql.append(meaning_n);
                if(null != wiki_text)
                {
                    str_sql.append(",");
                    str_sql.append(wiki_text.getID());
                    wiki_text_id = wiki_text.getID();
                }
                str_sql.append(")");
                s.executeUpdate (str_sql.toString());
            } finally {
                s.close();
            }

            try {
                s = connect.conn.createStatement ();
                ResultSet rs = s.executeQuery ("SELECT LAST_INSERT_ID() as id");
                try {
                    if (rs.next ()) {
                        meaning = new TMeaning(rs.getInt("id"), lang_pos, lang_pos.getID(),
                                                meaning_n, wiki_text, wiki_text_id);
                        //System.out.println("TMeaning.insert()):: wiki_text='" + wiki_text.getText() + "'; meaning_n=" + meaning_n);
                    }
                } finally {
                    rs.close();
                }
            } finally {
                s.close();
            }
        }catch(SQLException ex) {
            System.out.println("SQLException (wikt_parsed TMeaning.java insert()):: sql='" + str_sql.toString() + "' " + ex.getMessage());
        }
        return meaning;
    }

    /** Selects rows from the table 'meaning' by the lang_pos_id.
     * SELECT id,meaning_n,wiki_text_id FROM meaning WHERE lang_pos_id=1;
     * @return empty array if data is absent
     */
    public static TMeaning[] get (Connect connect,TLangPOS lang_pos) {

        if(null == lang_pos) {
            System.out.println("Error (wikt_parsed TMeaning.get()):: null argument lang_pos");
            return null;
        }
        StringBuilder str_sql = new StringBuilder();
        List<TMeaning> list_meaning = null;
        try {
            Statement s = connect.conn.createStatement ();
            try {
                str_sql.append("SELECT id,meaning_n,wiki_text_id FROM meaning WHERE lang_pos_id=");
                str_sql.append(lang_pos.getID());
                //str_sql.append(" ORDER BY id");
                ResultSet rs = s.executeQuery (str_sql.toString());
                try {
                    while (rs.next ())
                    {
                        int       id            = rs.getInt("id");
                        int       meaning_n     = rs.getInt("meaning_n");
                        int       wiki_text_id  = rs.getInt("wiki_text_id");
                        TWikiText wiki_text     = wiki_text_id < 1 ? null : TWikiText.getByID(connect, wiki_text_id);
                        if(null == list_meaning)
                                   list_meaning = new ArrayList<TMeaning>();
                        list_meaning.add(new TMeaning(id, lang_pos, lang_pos.getID(),
                                                      meaning_n, wiki_text, wiki_text_id));
                    }
                } finally {
                    rs.close();
                }
            } finally {
                s.close();
            }
        } catch(SQLException ex) {
            System.out.println("SQLException (TMeaning.get()):: sql='" + str_sql.toString() + "' " + ex.getMessage());
        }
        if(null == list_meaning)
            return NULL_TMEANING_ARRAY;
        return ((TMeaning[])list_meaning.toArray(NULL_TMEANING_ARRAY));
    }

    /** Filters the array 'rels' of semantic relations only for one kind of
     * semantic relations (e.g. synonyms) defined by the variable 'r_type'.
     * @param rels      array of relations
     * @param r_type    the relation type we are interested in, e.g. synonymy
     * @return an empty array if relations are absent
     */
    private void addOneKindOfRelation (Relation r_type, TRelation[] rels) {

        // 1. counts number of relations of this type
        int c = 0;
        for(TRelation r : rels) {
            if(r.getRelationType() == r_type)
                c ++;
        }
        if(0 == c)
            return;

        // 2. gets these relations
        TRelation[] result = new TRelation[c];
        
        c = 0;
        for(TRelation r : rels) {
            if(r.getRelationType() == r_type)
                result [c ++] = r;
        }

        if(0 == relation.size())
            relation = new HashMap<Relation, TRelation[]>();
        relation.put(r_type, result);
        //System.out.println("r_type="+r_type.toString()+"; +1 where TRelation[] result.size="+result.length);
    }

    /** Fills the relations field. */
    public void fillRelation (Connect connect) {

        TRelation[] rels = TRelation.get(connect, this);

        // let's convert the flat array: TRelation[] rels
        // into the map: Map<Relation, TRelation[]> relation;
        
        addOneKindOfRelation (Relation.synonymy, rels);
        addOneKindOfRelation (Relation.antonymy, rels);
        addOneKindOfRelation (Relation.hypernymy, rels);
        addOneKindOfRelation (Relation.hyponymy, rels);
        addOneKindOfRelation (Relation.holonymy, rels);
        addOneKindOfRelation (Relation.meronymy, rels);
    }
    
    /** Fills the translation field.
     *
     * Selects rows from the table 'translation' by the meaning_id,
     * fills (recursively) all fields translation_entry.
     */
    public void fillTranslation (Connect connect) {

        translation = TTranslation.getByMeaning(connect, this);

        if(null == translation)
            return;

        translation.getRecursive(connect);
    }

    /** Selects rows from the table 'meaning' by the lang_pos_id.
     * fills (recursively) relations.
     *
     * @return empty array if data is absent
     */
    public static TMeaning[] getRecursive (Connect connect,TLangPOS lang_pos) {
        
        TMeaning[] mm = TMeaning.get(connect, lang_pos);
        for(TMeaning m : mm) {
            m.fillRelation(connect);
            m.fillTranslation(connect);
        }

        return mm;
    }

    /** Selects rows from the table 'meaning' by ID<br><br>
     * SELECT lang_pos_id,meaning_n,wiki_text_id FROM meaning WHERE id=1;
     * @return empty array if data is absent
     */
    public static TMeaning getByID (Connect connect,int id) {
        
        StringBuilder str_sql = new StringBuilder();
        TMeaning meaning = null;
        
        try {
            Statement s = connect.conn.createStatement ();
            try {
                str_sql.append("SELECT lang_pos_id,meaning_n,wiki_text_id FROM meaning WHERE id=");
                str_sql.append(id);
                ResultSet rs = s.executeQuery (str_sql.toString());
                try {
                    if (rs.next ())
                    {
                        TLangPOS lang_pos = TLangPOS.getByID(connect,   rs.getInt("lang_pos_id"));
                        int meaning_n     =                             rs.getInt("meaning_n");
                        int wiki_text_id  =                             rs.getInt("wiki_text_id");
                        TWikiText wiki_text = wiki_text_id < 1 ? null : TWikiText.getByID(connect, wiki_text_id);
                        if(null != lang_pos) {
                            meaning = new TMeaning(id, lang_pos, lang_pos.getID(), meaning_n, wiki_text, wiki_text_id);
                        }
                    }
                } finally {
                    rs.close();
                }
            } finally {
                s.close();
            }
        } catch(SQLException ex) {
            System.out.println("SQLException (TMeaning.getByID()):: sql='" + str_sql.toString() + "' " + ex.getMessage());
        }
        return meaning;
    }

    /** Deletes row from the table 'meaning' by a value of ID.<br>
     *  DELETE FROM meaning WHERE id=1;
     * @param  id  unique ID in the table `meaning`
     */
    public static void delete (Connect connect,TMeaning meaning) {

        if(null == meaning) {
            System.out.println("Error (wikt_parsed TMeaning.delete()):: null argument meaning");
            return;
        }
        StringBuilder str_sql = new StringBuilder();
        try {
            Statement s = connect.conn.createStatement ();
            try {
                str_sql.append("DELETE FROM meaning WHERE id=");
                str_sql.append(meaning.getID());
                s.execute (str_sql.toString());
                //System.out.println("TMeaning.delete()):: wiki_text='" + meaning.getWikiText().getText() +
                //        "'; meaning_n=" + meaning.getMeaningNumber());
            } finally {
                s.close();
            }
        } catch(SQLException ex) {
            System.out.println("SQLException (TMeaning.delete()):: sql='" + str_sql.toString() + "' " + ex.getMessage());
        }
    }


    /** Checks whether the article 'page_title' has at least one translatio
     * into the destination (target) language from the array 'trans_lang'.
     * The fields 'lang_pos', 'lang_pos.translation' are scanned here.
     */
    public boolean hasTranslation(TLang trans_lang[]) {

        if(null == translation)
            return false;
        
        TTranslationEntry[] trans_entries = translation.getTranslationEntry();

        for(TTranslationEntry entry: trans_entries) {
            for(TLang lang : trans_lang) {
                if(lang == entry.getLang())
                    return true;
            }
        }
        
        return false;
    }
}
