/*
 * ====================================================================
 * Copyright (c) 2004-2010 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.tmatesoft.svn.core.internal.wc17.db.statement;

import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.table.ISqlJetTransaction;
import org.tmatesoft.sqljet.core.table.SqlJetDb;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetStatement;

/**
 * @version 1.4
 * @author TMate Software Ltd.
 */
public class SVNWCDbCreateSchema extends SVNSqlJetStatement {

    public static final Statement[] MAIN_DB_STATEMENTS = new Statement[] {
            new Statement(Type.TABLE, "REPOSITORY", "CREATE TABLE REPOSITORY ( id INTEGER PRIMARY KEY AUTOINCREMENT, root  TEXT UNIQUE NOT NULL, uuid  TEXT NOT NULL ); "),
            new Statement(Type.INDEX, "I_UUID", "CREATE INDEX I_UUID ON REPOSITORY (uuid); "),
            new Statement(Type.INDEX, "I_ROOT", "CREATE INDEX I_ROOT ON REPOSITORY (root); "),
            
            new Statement(Type.TABLE, "WCROOT", "CREATE TABLE WCROOT ( id  INTEGER PRIMARY KEY AUTOINCREMENT, local_abspath  TEXT UNIQUE ); "),
            new Statement(Type.INDEX, "I_LOCAL_ABSPATH", "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); "),
            new Statement(Type.TABLE, "PRISTINE", "CREATE TABLE PRISTINE ( checksum  TEXT NOT NULL PRIMARY KEY, compression  INTEGER, size  INTEGER NOT NULL, "
                    + "  refcount  INTEGER NOT NULL, md5_checksum  TEXT NOT NULL ); "),
            
            new Statement(Type.TABLE, "ACTUAL_NODE", "CREATE TABLE ACTUAL_NODE ( wc_id  INTEGER NOT NULL REFERENCES WCROOT (id), local_relpath  TEXT NOT NULL, parent_relpath  TEXT, "
                    + "  properties  BLOB, conflict_old  TEXT, conflict_new  TEXT, conflict_working  TEXT, prop_reject  TEXT, changelist  TEXT, "
                    + "  text_mod  TEXT, tree_conflict_data  TEXT, conflict_data  BLOB, older_checksum  TEXT, left_checksum  TEXT, right_checksum  TEXT, PRIMARY KEY (wc_id, local_relpath) ); "),
            new Statement(Type.INDEX, "I_ACTUAL_PARENT", "CREATE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath); "),
            new Statement(Type.TABLE, "LOCK", "CREATE TABLE LOCK ( repos_id  INTEGER NOT NULL REFERENCES REPOSITORY (id), repos_relpath  TEXT NOT NULL, lock_token  TEXT NOT NULL, "
                    + "  lock_owner TEXT, lock_comment TEXT, lock_date INTEGER, PRIMARY KEY (repos_id, repos_relpath) ); "),
            
            new Statement(Type.TABLE, "WORK_QUEUE", "CREATE TABLE WORK_QUEUE ( id  INTEGER PRIMARY KEY AUTOINCREMENT, work  BLOB NOT NULL ); "),
            new Statement(Type.TABLE, "WC_LOCK", "CREATE TABLE WC_LOCK ( wc_id  INTEGER NOT NULL  REFERENCES WCROOT (id), local_dir_relpath  TEXT NOT NULL, "
                    + "  locked_levels  INTEGER NOT NULL DEFAULT -1, PRIMARY KEY (wc_id, local_dir_relpath) ); "),
            new Statement(Type.TABLE, "NODES", "CREATE TABLE NODES ( wc_id  INTEGER NOT NULL REFERENCES WCROOT (id), local_relpath  TEXT NOT NULL, op_depth INTEGER NOT NULL, "
                    + "  parent_relpath  TEXT, repos_id  INTEGER REFERENCES REPOSITORY (id), repos_path  TEXT, revision  INTEGER, presence  TEXT NOT NULL, "
                    + "  moved_here  INTEGER, moved_to  TEXT, kind  TEXT NOT NULL, properties  BLOB, depth  TEXT, checksum  TEXT, symlink_target  TEXT, "
                    + "  changed_revision  INTEGER, changed_date INTEGER, changed_author TEXT, translated_size  INTEGER, last_mod_time  INTEGER, "
                    + "  dav_cache  BLOB, file_external INTEGER, inherited_props  BLOB, PRIMARY KEY (wc_id, local_relpath, op_depth) ); "),
                    
            //  this index is now created after update operation for performance reasons.
            new Statement(Type.INDEX, "I_NODES_MOVED", "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth);", false, false, true),
                    
            new Statement(Type.INDEX, "I_NODES_PARENT", "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, local_relpath, op_depth); "),
            new Statement(Type.INDEX, "I_PRISTINE_MD5", "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum);"),

            new Statement(Type.TABLE, "EXTERNALS", "CREATE TABLE EXTERNALS ( " +
            "  wc_id  INTEGER NOT NULL REFERENCES WCROOT (id), " +
            "  local_relpath  TEXT NOT NULL, " +
            "  parent_relpath  TEXT NOT NULL, " +
            "  repos_id  INTEGER NOT NULL REFERENCES REPOSITORY (id), " +
            "  presence  TEXT NOT NULL, " +
            "  kind  TEXT NOT NULL, " +
            "  def_local_relpath         TEXT NOT NULL, " +
            "  def_repos_relpath         TEXT NOT NULL, " +
            "  def_operational_revision  TEXT, " +
            "  def_revision              TEXT, " +
            "  PRIMARY KEY (wc_id, local_relpath) " +
            "); "),
            new Statement(Type.INDEX, "I_EXTERNALS_DEFINED", "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS " +
            		" (wc_id, def_local_relpath, local_relpath); " ),

    		new Statement(Type.VIEW, "NODES_BASE", "CREATE VIEW NODES_BASE AS SELECT * FROM nodes WHERE op_depth = 0;"),
            new Statement(Type.VIEW, "NODES_CURRENT", "CREATE VIEW NODES_CURRENT AS SELECT * FROM nodes AS n WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 WHERE n2.wc_id = n.wc_id AND n2.local_relpath = n.local_relpath);"),
            
            new Statement(Type.TRIGGER, "nodes_insert_trigger", "CREATE TRIGGER nodes_insert_trigger AFTER INSERT ON nodes WHEN NEW.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; END;"),
            new Statement(Type.TRIGGER, "nodes_delete_trigger", "CREATE TRIGGER nodes_delete_trigger AFTER DELETE ON nodes WHEN OLD.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END;"),
            new Statement(Type.TRIGGER, "nodes_update_checksum_trigger", "CREATE TRIGGER nodes_update_checksum_trigger AFTER UPDATE OF checksum ON nodes WHEN NEW.checksum IS NOT OLD.checksum BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END;"),
    };
    
    public static final Statement[] TARGETS_LIST = new Statement[] {
        new Statement(Type.TABLE, "TARGETS_LIST", "CREATE TABLE TARGETS_LIST (wc_id  INTEGER NOT NULL, local_relpath TEXT NOT NULL, parent_relpath TEXT, kind TEXT NOT NULL, PRIMARY KEY (wc_id, local_relpath) );", true),
        new Statement(Type.INDEX, "targets_list_kind", "CREATE INDEX targets_list_kind ON TARGETS_LIST (kind);", true),
    };
    
    public static final Statement[] DROP_TARGETS_LIST = new Statement[] {
        new Statement(Type.INDEX, "targets_list_kind", "targets_list_kind", true, true, false),
        new Statement(Type.TABLE, "TARGETS_LIST", "TARGETS_LIST", true, true, false),
    };
    
    public static final Statement[] NODE_PROPS_CACHE = new Statement[] {
        new Statement(Type.TABLE, "NODE_PROPS_CACHE", "CREATE TABLE NODE_PROPS_CACHE (local_Relpath TEXT NOT NULL, kind TEXT NOT NULL, properties BLOB, PRIMARY KEY (local_Relpath) );", true),
    };
    
    public static final Statement[] DROP_NODE_PROPS_CACHE = new Statement[] {
        new Statement(Type.TABLE, "NODE_PROPS_CACHE", "NODE_PROPS_CACHE", true, true, false),
    };
    
    public static final Statement[] DELETE_LIST = new Statement[] {
        new Statement(Type.TABLE, "DELETE_LIST", "CREATE TABLE DELETE_LIST (local_relpath TEXT PRIMARY KEY NOT NULL);", true),
    };
    
    public static final Statement[] DROP_DELETE_LIST = new Statement[] {
        new Statement(Type.TABLE, "DELETE_LIST", "DELETE_LIST", true, true, false),
    };
    
    public static final Statement[] REVERT_LIST = new Statement[] {
        new Statement(Type.TABLE, "REVERT_LIST", "CREATE TABLE REVERT_LIST (" +
        		" local_relpath TEXT NOT NULL, " +
        		" actual INTEGER NOT NULL, " +
        		" conflict_data BLOB, " +
                " notify INTEGER, " +
                " op_depth INTEGER, " +
                " repos_id INTEGER, " +
                " kind TEXT, " + 
                " PRIMARY KEY (local_relpath, actual) );", true)
    };
    
    public static final Statement[] DROP_REVERT_LIST = new Statement[] {
        new Statement(Type.TABLE, "REVERT_LIST", "REVERT_LIST", true, true, false),
    };
    
    public static final Statement[] CHANGELIST_LIST = new Statement[] {
        new Statement(Type.TABLE, "CHANGELIST_LIST", "CREATE TABLE CHANGELIST_LIST (" +
        		" wc_id  INTEGER NOT NULL REFERENCES WCROOT (id), " +
        		" local_relpath TEXT NOT NULL, " +
        		" notify INTEGER, " +
        		" changelist TEXT NOT NULL, PRIMARY KEY (wc_id, local_relpath) );" 
                , true) //,
        //new Statement(Type.INDEX, "changelist_list_index", "CREATE INDEX changelist_list_index ON changelist_list (wc_id, local_relpath);", true)        
    };
    
    public static final Statement[] DROP_CHANGELIST_LIST = new Statement[] {
        new Statement(Type.TABLE, "CHANGELIST_LIST", "CHANGELIST_LIST", true, true, false),
        //new Statement(Type.INDEX, "changelist_list_index", "changelist_list_index", true, true),
        new Statement(Type.TABLE, "TARGETS_LIST", "TARGETS_LIST", true, true, false),
        new Statement(Type.INDEX, "targets_list_kind", "targets_list_kind", true, true, false)
    };
    
    public static final Statement[] CREATE_UPDATE_MOVE_LIST = new Statement[] {
        new Statement(Type.TABLE, "UPDATE_MOVE_LIST", "CREATE TABLE UPDATE_MOVE_LIST (\n" +
                "  local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE,\n" +
                "  action INTEGER NOT NULL,\n" +
                "  kind  INTEGER NOT NULL,\n" +
                "  content_state INTEGER NOT NULL,\n" +
                "  prop_state  INTEGER NOT NULL\n" +
                "  )", true)
    };

    public static final Statement[] FINALIZE_UPDATE_MOVE = new Statement[] {
        new Statement(Type.TABLE, "UPDATE_MOVE_LIST", "UPDATE_MOVE_LIST", true, true, false)
    };

    private enum Type {
        TABLE, INDEX, VIEW, TRIGGER;
    }

    public static class Statement {

        private Type type;
        private String sql;
        private boolean isDrop;
        private String name;
        private boolean isDropBeforeCreate;
        private boolean isRunOnUpgrade;
        
        public Statement(Type type, String name, String sql) {
            this(type, name, sql, false, false, false);
        }

        public Statement(Type type, String name, String sql, boolean dropBeforeCreate) {
            this(type, name, sql, dropBeforeCreate, false, false);
        }

        public Statement(Type type, String name, String sql, boolean dropBeforeCreate, boolean isDrop, boolean upgradeOnly) {
            this.type = type;
            this.sql = sql;
            this.name = name;
            this.isDrop = isDrop;
            this.isDropBeforeCreate = dropBeforeCreate;
            this.isRunOnUpgrade = upgradeOnly;
        }
        
        public boolean isDrop() {
            return isDrop;
        }

        public Type getType() {
            return type;
        }

        public String getSql() {
            return sql;
        }
        
        public String getName() {
            return name;
        }
        
        public boolean isDropBeforeCreate() {
            return isDropBeforeCreate;
        }
        
        private boolean isRunOnUpgradeOnly() {
            return isRunOnUpgrade;
        }
    }

    private Statement[] statements;
    private int userVersion;

    private boolean isUpgrade;

    public SVNWCDbCreateSchema(SVNSqlJetDb sDb, Statement[] statements, int userVersion, boolean isUpgrade) {        
        super(sDb);
        this.statements = statements;
        this.userVersion = userVersion;
        this.isUpgrade = isUpgrade;
    }

    public long exec() throws SVNException {
        try {
            sDb.getDb().runWriteTransaction(new ISqlJetTransaction() {

                public Object run(SqlJetDb db) throws SqlJetException {
                    for (Statement stmt : statements) {
                        switch (stmt.getType()) {
                            case TABLE:                                
                                if (stmt.isDrop()) {
                                    if (db.getSchema().getTableNames().contains(stmt.getName())) {
                                        db.dropTable(stmt.getSql());
                                    }
                                } else {
                                    if (stmt.isDropBeforeCreate()) {
                                        if (db.getSchema().getTableNames().contains(stmt.getName())) {
                                            db.dropTable(stmt.getName());
                                        }
                                    }
                                    db.createTable(stmt.getSql()); 
                                }
                                break;
                            case INDEX:
                                if (stmt.isDrop()) {
                                    if (db.getSchema().getIndexNames().contains(stmt.getName())) {
                                        db.dropIndex(stmt.getSql());
                                    }
                                } else {
                                    if (stmt.isDropBeforeCreate()) {
                                        if (db.getSchema().getIndexNames().contains(stmt.getName())) {
                                            db.dropIndex(stmt.getName());
                                        }
                                    }
                                    if (stmt.isRunOnUpgradeOnly()) {
                                        if (isUpgrade) {
                                            db.createIndex(stmt.getSql()); 
                                        }
                                    } else {
                                        db.createIndex(stmt.getSql()); 
                                    }
                                }
                                break;
                            case VIEW:
                                if (stmt.isDrop()) {
                                    if (db.getSchema().getViewNames().contains(stmt.getName())) {
                                        db.dropView(stmt.getSql());
                                    }
                                } else {
                                    if (stmt.isDropBeforeCreate()) {
                                        if (db.getSchema().getViewNames().contains(stmt.getName())) {
                                            db.dropView(stmt.getName());
                                        }
                                    }
                                    db.createView(stmt.getSql()); 
                                }
                                break;
                            case TRIGGER:
                                if (stmt.isDrop()) {
                                    if (db.getSchema().getTriggerNames().contains(stmt.getName())) {
                                        db.dropTrigger(stmt.getSql());
                                    }
                                } else {
                                    if (stmt.isDropBeforeCreate()) {
                                        if (db.getSchema().getTriggerNames().contains(stmt.getName())) {
                                            db.dropTrigger(stmt.getName());
                                        }
                                    }
                                    db.createTrigger(stmt.getSql()); 
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    if (userVersion >= 0) {
                        db.getOptions().setUserVersion(userVersion);
                    }
                    return null;
                }
            });
        } catch (SqlJetException e) {
            SVNSqlJetDb.createSqlJetError(e);
        }
        return 0;
    }

}
