/*
 * Copyright 2007 - 2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.sf.jailer;

import java.io.FileWriter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import net.sf.jailer.database.SQLDialect;
import net.sf.jailer.database.Session;
import net.sf.jailer.datamodel.Association;
import net.sf.jailer.datamodel.Column;
import net.sf.jailer.datamodel.DataModel;
import net.sf.jailer.datamodel.RowIdSupport;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.entitygraph.EntityGraph;
import net.sf.jailer.util.CellContentConverter;
import net.sf.jailer.util.Quoting;

import org.apache.log4j.Logger;

/**
 * Creates a listing of all association-paths of exported entities 
 * back to a subject in an entity-graph (the 'explain.log' file)
 * 
 * @author Ralf Wisser
 */
public class ExplainTool {

	/**
     * The logger.
     */
    private static final Logger _log = Logger.getLogger(ExplainTool.class);

    /**
     * Creates a listing of all association-paths of exported entities 
     * back to a subject in an entity-graph (the 'explain.log' file)
     * 
     * @param graph
     *            the entity-graph to explain
     * @param session
     *            for executing SQL-statements
     */
    public static void explain(final EntityGraph graph, final Session session) throws Exception {
        _log.info("generating explain.log...");
        final Quoting quoting = new Quoting(session);
        StringBuffer succEqualsE = new StringBuffer();
        for (Column column: graph.getUniversalPrimaryKey().getColumns()) {
            if (succEqualsE.length() > 0) {
                succEqualsE.append(" and ");
            }
            succEqualsE.append("Succ.PRE_" + column.name + "=E." + column.name);
        }
        final FileWriter writer = new FileWriter("explain.log");
        final RowIdSupport rowIdSupport = new RowIdSupport(graph.getDatamodel(), Configuration.forDbms(session));
        String selectLeafs = "Select type, " + graph.getUniversalPrimaryKey().columnList(null) + " From " + SQLDialect.dmlTableReference(EntityGraph.ENTITY, session) + " E Where E.r_entitygraph=" + graph.graphID +
            " and not exists (Select * from " + SQLDialect.dmlTableReference(EntityGraph.ENTITY, session) + " Succ Where Succ.r_entitygraph=" + graph.graphID + " and Succ.PRE_TYPE=E.type and " + succEqualsE + ")";
        session.executeQuery(selectLeafs, new Session.AbstractResultSetReader() {
        	public void readCurrentRow(ResultSet resultSet) throws SQLException {
        		int o = resultSet.getInt(1);
        		String type = null;
                if (!resultSet.wasNull()) {
                	Table tableByTypeName = graph.getDatamodel().getTableByOrdinal(o);
                	type = tableByTypeName.getName();
                }
                List<String> keys = new ArrayList<String>();
                int i = 2;
                CellContentConverter cellContentConverter = getCellContentConverter(resultSet, session);
				for (@SuppressWarnings("unused") Column column: graph.getUniversalPrimaryKey().getColumns()) {
                    keys.add(cellContentConverter.toSql(cellContentConverter.getObject(resultSet, i++)));
                }
                try {
                    writer.append(path(graph, session, type, keys, graph.getDatamodel(), rowIdSupport, quoting));
                    writer.append(".\n");
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        writer.append("\n");
        List<String> associations = new ArrayList<String>();
        for (Map.Entry<Association, Integer> e: graph.explainIdOfAssociation.entrySet()) {
            String nr = "" + e.getValue();
            while (nr.length() < 5) {
                nr = " " + nr;
            }
            String sourceName = e.getKey().source.getName();
            while (sourceName.length() < 24) {
                sourceName = sourceName + " ";
            }
            associations.add("#" + nr + " " + sourceName + " -> " + e.getKey());
        }
        Collections.sort(associations);
        for (String line: associations) {
            writer.append(line);
            writer.append("\n");
        }
        writer.close();
    }

    /**
     * Stringifies the path of predecessors of a given entity.
     * @param rowIdSupport 
     * @throws SQLException 
     */
    private static String path(final EntityGraph graph, final Session session, String type, List<String> keys, DataModel datamodel, RowIdSupport rowIdSupport, Quoting quoting) throws SQLException {
        String where = "";
        int i = 0;
        for (Column column: graph.getUniversalPrimaryKey().getColumns()) {
            if (where.length() > 0) {
                where += " and ";
            }
            String value = keys.get(i++);
			where += quoting.quote(column.name) + (value == null || value.equals("null")? " is null" : ("=" + value));
        }
        String selectPredecessor = "Select PRE_TYPE, association, " + graph.getUniversalPrimaryKey().columnList("PRE_") + " From " + SQLDialect.dmlTableReference(EntityGraph.ENTITY, session) + " E Where E.r_entitygraph=" + graph.graphID +
            " and type=" + datamodel.getTable(type).getOrdinal() + " and " + where;
        final String preType[] = new String[1];
        final List<String> preKeys = new ArrayList<String>();
        final Integer associationID[] = new Integer[1];
        session.executeQuery(selectPredecessor, new Session.AbstractResultSetReader() {
            public void readCurrentRow(ResultSet resultSet) throws SQLException {
            	int o = resultSet.getInt(1);
                if (!resultSet.wasNull()) {
                	Table tableByTypeName = graph.getDatamodel().getTableByOrdinal(o);
                	preType[0] = tableByTypeName.getName();
                }
                associationID[0] = resultSet.getInt(2);
                int i = 3;
                CellContentConverter cellContentConverter = getCellContentConverter(resultSet, session);
				for (@SuppressWarnings("unused") Column column: graph.getUniversalPrimaryKey().getColumns()) {
                    preKeys.add(cellContentConverter.toSql(cellContentConverter.getObject(resultSet, i++)));
                }
            }
        });
        String thePath = entityAsString(type, keys, datamodel, rowIdSupport, session);
        if (preType[0] != null) {
            thePath = path(graph, session, preType[0], preKeys, datamodel, rowIdSupport, quoting) + " --" + associationID[0] + "--> " + thePath;
        }
        return thePath;
    }

    /**
     * Stringifies an entity.
     * @param session 
     */
    private static String entityAsString(String type, List<String> keys, DataModel datamodel, RowIdSupport rowIdSupport, Session session) {
        StringBuffer sb = new StringBuffer(type + "(");
        int i = 0, ki = 0;
        Map<Column, Column> match = rowIdSupport.getUniversalPrimaryKey().match(rowIdSupport.getPrimaryKey(datamodel.getTable(type)));
        for (Column column: rowIdSupport.getUniversalPrimaryKey().getColumns()) {
            if (match.get(column) != null) {
                if (i > 0) {
                    sb.append(", ");
                }
                ++i;
                sb.append(keys.get(ki));
            }
            ++ki;
        }
        sb.append(")");
        return sb.toString();
    }
    
}
