/*
 * WANDORA
 * Knowledge Extraction, Management, and Publishing Application
 * http://wandora.org
 * 
 * Copyright (C) 2004-2016 Wandora Team
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * 
 */




package org.wandora.application.gui.search;


import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelListener;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.swing.JScrollPane;
import javax.swing.table.DefaultTableModel;
import org.wandora.application.Wandora;
import org.wandora.application.WandoraScriptManager;
import org.wandora.application.contexts.Context;
import org.wandora.application.contexts.LayeredTopicContext;
import org.wandora.application.gui.TopicSelector;
import org.wandora.application.gui.UIBox;
import org.wandora.application.gui.WandoraOptionPane;
import org.wandora.application.gui.simple.SimpleButton;
import org.wandora.application.gui.simple.SimpleComboBox;
import org.wandora.application.gui.simple.SimpleLabel;
import org.wandora.application.gui.simple.SimpleScrollPane;
import org.wandora.application.gui.simple.SimpleTextPaneResizeable;
import org.wandora.application.gui.table.MixedTopicTable;
import org.wandora.application.gui.tree.TopicTreePanel;
import org.wandora.query2.Directive;
import org.wandora.query2.QueryContext;
import org.wandora.query2.QueryException;
import org.wandora.query2.ResultRow;
import org.wandora.query2.Static;
import org.wandora.topicmap.Topic;
import org.wandora.topicmap.TopicMap;
import org.wandora.topicmap.TopicMapException;
import org.wandora.utils.Options;
import org.wandora.utils.Tuples;

/**
 *
 * @author akivela
 */


public class QueryPanel extends javax.swing.JPanel implements TopicSelector {

    private Wandora wandora = null;
    private String SCRIPT_QUERY_OPTION_KEY = "scriptQueries";
    private List<Tuples.T3<String,String,String>> storedQueryScripts = new ArrayList<Tuples.T3<String,String,String>>();
    private MixedTopicTable resultsTable = null;
    private SimpleLabel message = null;
    
    
    
    /**
     * Creates new form QueryPanel
     */
    public QueryPanel() {
        wandora = Wandora.getWandora();
        initComponents();
        message = new SimpleLabel();
        message.setHorizontalAlignment(SimpleLabel.CENTER);
        message.setIcon(UIBox.getIcon("gui/icons/warn.png"));
        scriptTextPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
        engineComboBox.setEditable(false);
        List<String> engines=WandoraScriptManager.getAvailableEngines();
        engineComboBox.removeAllItems();
        for(String e : engines) {
            if(e != null && e.length() > 0) {
                engineComboBox.addItem(e);
            }
        }
        queryComboBox.setEditable(false);
        clearResultsButton.setEnabled(false);
        readStoredScriptQueries();
    }

    
    
    public void removeResultScrollPanesMouseListeners() {
        MouseWheelListener[] mouseWheelListeners = resultScrollPane.getMouseWheelListeners();
        for(MouseWheelListener listener : mouseWheelListeners) {
            resultScrollPane.removeMouseWheelListener(listener);
        }
    }
    
    
    
    private void readStoredScriptQueries() {
        storedQueryScripts = new ArrayList<Tuples.T3<String,String,String>>();
        if(wandora != null) {
            Options options = wandora.getOptions();
            if(options != null) {
                int queryCount = 0;
                String queryScript = null;
                String queryEngine = null;
                String queryName = options.get(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].name");
                while(queryName != null && queryName.length() > 0) {
                    queryScript = options.get(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].script");
                    queryEngine = options.get(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].engine");
                    storedQueryScripts.add( new Tuples.T3(queryName, queryEngine, queryScript) );
                    queryCount++;
                    queryName = options.get(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].name");
                }
                updateQueryComboBox();
            }
        }
    }


    private void writeScriptQueries() {
        if(wandora != null) {
            Options options = wandora.getOptions();
            if(options != null) {
                options.removeAll(SCRIPT_QUERY_OPTION_KEY);
                int queryCount = 0;
                for( Tuples.T3<String,String,String> storedQuery : storedQueryScripts ) {
                    if(storedQuery != null) {
                        options.put(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].name", storedQuery.e1);
                        options.put(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].engine", storedQuery.e2);
                        options.put(SCRIPT_QUERY_OPTION_KEY+".query["+queryCount+"].script", storedQuery.e3);
                        queryCount++;
                    }
                }
            }
        }
    }


    public void updateQueryComboBox() {
        queryComboBox.removeAllItems();
        String name = "";
        String script = "";
        String engine = "";
        for( Tuples.T3<String,String,String> storedQuery : storedQueryScripts ) {
            if(storedQuery != null) {
                name = storedQuery.e1;
                engine = storedQuery.e2;
                script = storedQuery.e3;
                queryComboBox.addItem(name);
            }
        }
        queryComboBox.setSelectedItem(name);
        engineComboBox.setSelectedItem(engine);
        scriptTextPane.setText(script);
    }


    public void addScriptQuery() {
        String queryName = WandoraOptionPane.showInputDialog(wandora, "Give name for the query script?", "", "Name of the query script");
        if(queryName != null && queryName.length() > 0) {
            String queryEngine = engineComboBox.getSelectedItem().toString();
            String queryScript = scriptTextPane.getText();
            storedQueryScripts.add( new Tuples.T3(queryName, queryEngine, queryScript) );
            writeScriptQueries();
            updateQueryComboBox();
        }
    }



    public void deleteScriptQuery() {
        int index = queryComboBox.getSelectedIndex();
        if(index < storedQueryScripts.size() && index >= 0) {
            String name = storedQueryScripts.get(index).e1;
            int a = WandoraOptionPane.showConfirmDialog(wandora, "Would you like to remove query script '"+name+"'?", "Delete query script?");
            if(a == WandoraOptionPane.YES_OPTION) {
                storedQueryScripts.remove(index);
                writeScriptQueries();
                updateQueryComboBox();
            }
        }
    }


    public void selectScriptQuery() {
        int index = queryComboBox.getSelectedIndex();
        if(index < storedQueryScripts.size() && index >= 0) {
            Tuples.T3<String,String,String> query = storedQueryScripts.get(index);
            // queryComboBox.setSelectedIndex(index);
            engineComboBox.setSelectedItem(query.e2);
            scriptTextPane.setText(query.e3);
        }
    }
    
    public MixedTopicTable getTopicsByQuery(Iterator<Topic> contextTopics) throws ScriptException, TopicMapException, Exception {
        String engineName = engineComboBox.getSelectedItem().toString();
        String scriptStr =  scriptTextPane.getText();
        return getTopicsByQuery(wandora,engineName,scriptStr,contextTopics);
    }
        
    public static MixedTopicTable getTopicsByQuery(Wandora wandora,TopicMap tm,Directive query,Iterator<Topic> contextTopics) throws QueryException, TopicMapException {
        ArrayList<ResultRow> res = new ArrayList<>();
        if(contextTopics!=null){
            while(contextTopics.hasNext()){
                Topic t=contextTopics.next();
                if(t!=null && !t.isRemoved()) res.add( new ResultRow(t));
            }
        }

        QueryContext context=new QueryContext(tm, "en");

        // System.out.println("Query: "+query.debugString());

        if(res.isEmpty()){}
        else if(res.size()==1){
            res=query.doQuery(context, res.get(0));
        }
        else{
            res=query.from(new Static(res)).doQuery(context, res.get(0));
        }

        ArrayList<String> columns=new ArrayList<>();
        for(ResultRow row : res){
            for(int i=0;i<row.getNumValues();i++){
                String l=row.getRole(i);
                if(!columns.contains(l)) columns.add(l);
            }
        }
        ArrayList<Object> columnTopicsA=new ArrayList<>();
        for(int i=0;i<columns.size();i++){
            String l=columns.get(i);
            if(l.startsWith("~")){
                columns.remove(i);
                i--;
            }
            else{
                Topic t=tm.getTopic(l);
                if(t!=null) columnTopicsA.add(t);
                else columnTopicsA.add(l);
            }
        }
        Object[] columnTopics=columnTopicsA.toArray(new Object[columnTopicsA.size()]);
        if(res.size() > 0) {
            Object[][] data=new Object[res.size()][columns.size()];
            for(int i=0;i<res.size();i++){
                ResultRow row=res.get(i);
                ArrayList<String> roles=row.getRoles();
                for(int j=0;j<columns.size();j++){
                    String r=columns.get(j);
                    int ind=roles.indexOf(r);
                    if(ind!=-1) data[i][j]=row.getValue(ind);
                    else data[i][j]=null;
                }
            }

            MixedTopicTable table=new MixedTopicTable(wandora);
            table.initialize(data,columnTopics);
            return table;
        }
        return null;        
    }
    
    public static MixedTopicTable getTopicsByQuery(Wandora wandora,String engineName,String scriptStr,Iterator<Topic> contextTopics) throws ScriptException, TopicMapException, Exception {
        TopicMap tm = wandora.getTopicMap();
        WandoraScriptManager sm = new WandoraScriptManager();
        ScriptEngine engine = sm.getScriptEngine(engineName);
        Directive query = null;
        Object o=engine.eval(scriptStr);
        if(o==null) o=engine.get("query");
        if(o!=null && o instanceof Directive) {
            query = (Directive)o;
        }
        
        if(contextTopics==null || !contextTopics.hasNext()){
            // if context is empty just add some (root of a tree chooser) topic
            HashMap<String,TopicTreePanel> trees=wandora.getTopicTreeManager().getTrees();
            TopicTreePanel tree=trees.values().iterator().next();
            Topic t=tm.getTopic(tree.getRootSI());
            ArrayList<Topic> al=new ArrayList<>();
            al.add(t);
            contextTopics=al.iterator();
        }

        return getTopicsByQuery(wandora, wandora.getTopicMap(), query, contextTopics);
    }
    
    
    
    public void refresh() {
        if(resultsTable != null) {
            ((DefaultTableModel) resultsTable.getModel()).fireTableDataChanged();
        }
        resultPanel.revalidate();
        revalidate();
    }
    
    
    
    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        queryPanel = new javax.swing.JPanel();
        queryPanelInner = new javax.swing.JPanel();
        selectQueryPanel = new javax.swing.JPanel();
        queryComboBox = new SimpleComboBox();
        addQueryButton = new SimpleButton();
        delQueryButton = new SimpleButton();
        scriptQueryPanel = new javax.swing.JPanel();
        engineLabel = new SimpleLabel();
        engineComboBox = new SimpleComboBox();
        scriptLabel = new SimpleLabel();
        scriptScrollPane = new javax.swing.JScrollPane();
        scriptTextPane = new QueryTextPane();
        scripButtonPanel = new javax.swing.JPanel();
        runButton = new SimpleButton();
        clearResultsButton = new SimpleButton();
        resultContainerPanel = new javax.swing.JPanel();
        resultScrollPane = new SimpleScrollPane();
        resultPanel = new javax.swing.JPanel();

        setLayout(new java.awt.GridBagLayout());

        queryPanel.setLayout(new java.awt.GridBagLayout());

        queryPanelInner.setLayout(new java.awt.GridBagLayout());

        selectQueryPanel.setLayout(new java.awt.GridBagLayout());

        queryComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
        queryComboBox.setPreferredSize(new java.awt.Dimension(56, 25));
        queryComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                queryComboBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 4);
        selectQueryPanel.add(queryComboBox, gridBagConstraints);

        addQueryButton.setText("Add");
        addQueryButton.setMargin(new java.awt.Insets(1, 4, 1, 4));
        addQueryButton.setPreferredSize(new java.awt.Dimension(50, 25));
        addQueryButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addQueryButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 4);
        selectQueryPanel.add(addQueryButton, gridBagConstraints);

        delQueryButton.setText("Del");
        delQueryButton.setMargin(new java.awt.Insets(1, 4, 1, 4));
        delQueryButton.setPreferredSize(new java.awt.Dimension(50, 25));
        delQueryButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                delQueryButtonActionPerformed(evt);
            }
        });
        selectQueryPanel.add(delQueryButton, new java.awt.GridBagConstraints());

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
        queryPanelInner.add(selectQueryPanel, gridBagConstraints);

        scriptQueryPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        scriptQueryPanel.setLayout(new java.awt.GridBagLayout());

        engineLabel.setFont(org.wandora.application.gui.UIConstants.tabFont);
        engineLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        engineLabel.setText("Engine");
        engineLabel.setMinimumSize(new java.awt.Dimension(70, 14));
        engineLabel.setPreferredSize(new java.awt.Dimension(70, 14));
        gridBagConstrain