/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.ant;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.PropertyHelper;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * SQL scripts execution task.
 *
 * @author Sultan Tezadov
 */
public class DBTask extends Task {

    private String driver;
    private String connectionUrl;
    private String sql;
    private String sqlFile;
    private List filesets = new LinkedList();
    private String resultProperty;
    private String fromRegex;

    public void execute() throws BuildException {
        try {
            if(sql != null) {
                if(resultProperty == null) {
                    executeSql(new String[]{sql});
                } else {
                    String result = executeSqlWithResult(sql);
                    log("Setting new property: name='" + resultProperty +
                            "' value='" + result + "'", Project.MSG_VERBOSE);
                    PropertyHelper ph = PropertyHelper.getPropertyHelper(
                            getProject());
                    ph.setNewProperty(null, resultProperty, result);
                }
            } else if(sqlFile != null) {
                processFile(sqlFile);
            } else if(filesets != null) {
                processFileSet();
            }
        } catch (Exception ex) {
            throw new BuildException(ex);
        }
    }

    private void processFileSet() throws FileNotFoundException, IOException,
            ClassNotFoundException, SQLException {
        Iterator it = filesets.iterator();
        while(it.hasNext()) {
            FileSet fs = (FileSet) it.next();
            DirectoryScanner ds = fs.getDirectoryScanner(project);
            File basedir = ds.getBasedir();
            String[] files = ds.getIncludedFiles();
            int fromIndex = 0;
            if(fromRegex != null) {
                while((fromIndex < files.length) &&
                        (!files[fromIndex].matches(fromRegex))) {
                    log("Update script " + files[fromIndex] +
                            " does not match pattern '" +
                            fromRegex + "' -- ignoring", Project.MSG_VERBOSE);
                    fromIndex++;
                }
                if(fromIndex == files.length) {
                    log("No update scripts found in " + basedir +
                            " for the scripts latest version",
                            Project.MSG_WARN);
                }
            }
            for(int i = fromIndex; i < files.length; i++) {
                String file = basedir + "/" + files[i];
                processFile(file);
            }
        }
    }

    private Connection createConnection()
            throws ClassNotFoundException, SQLException {
        Class.forName(driver); // load driver
        Connection con = DriverManager.getConnection(
                connectionUrl); // get connection
        return con;
    }

    private void executeSql(String[] sqls)
            throws ClassNotFoundException, SQLException {
        Statement statement;
        Connection con = createConnection();
        if(sqls != null) {
            try {
                log("------------------------ Executing SQL: ----------------------",
                        Project.MSG_VERBOSE);
                statement = con.createStatement();
                for(int i = 0; i < sqls.length; i++) {
                    log("\n=== Batch:\n" + sqls[i], Project.MSG_VERBOSE);
                    statement.execute(sqls[i]);
                }
                log("------------------ SQL executed successfully -----------------",
                        Project.MSG_VERBOSE);
            } finally {
                con.close();
            }
        }
    }

    private String[] loadFile(String path)
            throws FileNotFoundException, IOException {
        FileReader fr = new FileReader(path);
        BufferedReader br = new BufferedReader(fr);
        StringBuffer sb = new StringBuffer();
        String line;
        LinkedList batches = new LinkedList();
        while((line = br.readLine()) != null) {
            line = line.trim();
            if("DELIMITER //".equalsIgnoreCase(line)) {
                // ignore this line
            } else {
                if("GO".equalsIgnoreCase(line) || "//".equalsIgnoreCase(line)) {
                    // GO directive -- create new batch
                    batches.add(sb.toString());
                    sb = new StringBuffer();
                } else { // strip the GO directive
                    sb.append(line);
                    sb.append("\n");
                }
            }
        }
        if(sb.toString().length() > 0) {
            batches.add(sb.toString());
        }
        fr.close();
        return (String[]) batches.toArray(new String[0]);
    }

    private void processFile(String sqlFile) throws
            FileNotFoundException, IOException, ClassNotFoundException,
            SQLException {
        log("Running SQL script: " + sqlFile);
        executeSql(loadFile(sqlFile));
    }

    private String executeSqlWithResult(String sql)
            throws ClassNotFoundException, SQLException {
        ResultSet rs = null;
        Statement statement;
        Connection con = createConnection();
        if(sql != null) {
            try {
                log("------------------------ Executing SQL: ----------------------",
                        Project.MSG_VERBOSE);
                statement = con.createStatement();
                log(sql, Project.MSG_VERBOSE);
                rs = statement.executeQuery(sql);
                log("------------------ SQL executed successfully -----------------",
                        Project.MSG_VERBOSE);
                if(rs.next()) {
                    return rs.getString(1);
                } else {
                    log("No results returned for the query: " + sql,
                            Project.MSG_WARN);
                }
            } finally {
                if(rs != null) {
                    rs.close();
                }
                con.close();
            }
        }
        return null;
    }


    public void setDriver(String driver) {
        this.driver = driver;
    }

    public void setConnectionUrl(String connectionUrl) {
        this.connectionUrl = connectionUrl;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getSqlFile() {
        return sqlFile;
    }

    public void setSqlFile(String sqlFile) {
        this.sqlFile = sqlFile;
    }

    public void addFileset(FileSet fs) {
        filesets.add(fs);
    }

    public String getResultProperty() {
        return resultProperty;
    }

    public void setResultProperty(String resultProperty) {
        this.resultProperty = resultProperty;
    }

    public String getFromRegex() {
        return fromRegex;
    }

    public void setFromRegex(String fromRegex) {
        this.fromRegex = fromRegex;
    }

}
