/**
 * Copyright (c) 2009--2014 Red Hat, Inc.
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package com.redhat.rhn.scripts;

import com.redhat.rhn.common.db.datasource.Mode;
import com.redhat.rhn.common.db.datasource.ModeFactory;
import com.redhat.rhn.common.db.datasource.SelectMode;
import com.redhat.rhn.common.hibernate.HibernateFactory;
import com.redhat.rhn.common.hibernate.HibernateRuntimeException;

import org.hibernate.HibernateException;
import org.hibernate.Session;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;

/**
 * Program to generate Exceptions.
 * Make sure that you have set rhn.checkout.dir in your
 * ~/.rhn.properties file.  Without that property, these tests will
 * _NOT_ run.  When the DataSource xml files get moved into the rhn-java
 * repo, that requirement will be removed.
 *
 * @version $Rev$
 */
public class ExplainPlanGenerator {
    private String outfile;

    private static final String QUERY_NAME = "ryan_query";

    /**
     * Create a new explain plan generator
     * @param source The path to the cvs rhn checkout.
     * @param output The file to write the plans to.
     */
    public ExplainPlanGenerator(String source, String output) {
        outfile = output;
    }

    private static final String EXPLAIN_QUERY = "select " +
                                   "to_char(parent_id) explain_parent_id, " +
                                   "to_char(id) explain_id, " +
                                   "lpad(' ',2*(LEVEL-1)) || operation || " +
                                   "'  ' || options || '  ' || object_name " +
                                   "explain_operation " +
                                   "from " +
                                   "    plan_table " +
                                   "start with id = 1 and statement_id = ? " +
                                   "connect by prior id = parent_id " +
                                   "    and statement_id = ?";

    private boolean shouldSkip(Mode m) {
        /* Don't do plans for queries that use system tables or for
         * dummy queries.
         */
        return (m != null && m.getQuery() != null &&
                (m.getName().equals("tablespace_overview") ||
                 m.getQuery().getOrigQuery().trim().startsWith("--")));
    }

    /** Execute the task
     *  @throws IOException If the output file can't be opened.
     *  @throws SQLException if something goes wrong with the DB.
     */
    public void execute() throws IOException, SQLException {
        Session session = null;
        Connection conn = null;
        try {
            session = HibernateFactory.getSession();
            PrintStream out = new PrintStream(new FileOutputStream(outfile));

            session.doWork((connection) -> {

                Collection fileKeys = ModeFactory.getKeys();

                TreeSet ts = new TreeSet(fileKeys);
                Iterator i = ts.iterator();
                while (i.hasNext()) {
                    String file = (String)i.next();
                    Map queries = ModeFactory.getFileKeys(file);
                    if (file.equals("test_queries")) {
                        continue;
                    }
                    out.println("\nFile:   " + file);

                    Iterator q = new TreeSet(queries.keySet()).iterator();
                    int count = 0;
                    while (q.hasNext()) {
                        Mode m = (Mode)queries.get(q.next());

                        /* Don't do plans for queries that use system tables or for
                         * dummy queries.
                         */
                        if (shouldSkip(m)) {
                            out.println("\nSkipping dummy query:  " + m.getName());
                            continue;
                        }
                        if (!(m instanceof SelectMode)) {
                            out.println("\nSkipping Write or Callable mode:" + m.getName());
                            continue;
                        }
                        out.println("\nPlan for " + m.getName());

                        String query = "EXPLAIN PLAN " +
                            "SET STATEMENT_ID='" + QUERY_NAME + "' FOR " +
                            m.getQuery().getOrigQuery();

                        // HACK!  Some of the queries actually have %s in them.
                        // So, replace all %s with :rbb so that the explain plan
                        // can be generated.
                        query = query.replaceAll("%s", ":rbb");

                        PreparedStatement ps = conn.prepareStatement(query);

                        ps.execute();
                        ps.close();

                        // Now that we have generated the explain plan, we just
                        // need to get it from the DB.
                        ps = conn.prepareStatement(EXPLAIN_QUERY);
                        ps.setString(1, QUERY_NAME);
                        ps.setString(2, QUERY_NAME);
                        ResultSet rs = ps.executeQuery();
                        while (rs.next()) {
                            String parentId = rs.getString("explain_parent_id");
                            String id = rs.getString("explain_id");
                            String operation = rs.getString("explain_operation");

                            out.println(parentId + " " + id + " " + operation);
                        }
                        count++;
                        rs.close();
                        ps.close();
                        Statement st = conn.createStatement();
                        st.execute("Delete FROM plan_table where " +
                                        "STATEMENT_ID='" + QUERY_NAME + "'");
                        st.close();

                    }
                }
                out.close();
            });
        }
        catch (HibernateException he) {
            throw new
                HibernateRuntimeException(
                    "HibernateException in ExplainPlanGenerator.", he);
        }
    }

    /**
     * Run the program
     * @param args The arguments to the java program.
     */
    public static void main(String[] args) {
        if (args.length != 2) {
            System.err.println("Not enough arguments, usage:");
            System.err.println("ExplainPlanGenerator /path/to/rhn " +
                               "/path/to/output_file");
            System.exit(1);
        }
        ExplainPlanGenerator ep = new ExplainPlanGenerator(args[0], args[1]);
        try {
            ep.execute();
            System.out.println("Done generating explain plans in " + args[1]);
        }
        catch (SQLException e) {
            System.err.println("Something went wrong in the DB: " +
                               e.getMessage());
        }
        catch (IOException e) {
            System.err.println("Couldn't write to disk: " + e.getMessage());
        }
    }

}