package com.usoftchina.uas.office.jdbc;

import com.usoftchina.uas.office.util.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author yingp
 * @date 2020/1/15
 */
public class SchemaUtils {

    public static TableBuilder newTable(String tableName) {
        return new TableBuilder(tableName);
    }

    private static boolean isTableExists(String tableName, JdbcTemplate jdbcTemplate) {
        int count = jdbcTemplate.queryForObject("select count(1) from user_tables where table_name=?",
                Integer.class, tableName.toUpperCase());
        return count > 0;
    }

    public static ColumnBuilder newColumn(String columnName) {
        return new ColumnBuilder(columnName);
    }

    private static boolean isColumnExists(String tableName, String columnName, JdbcTemplate jdbcTemplate) {
        int count = jdbcTemplate.queryForObject("select count(1) from user_tab_cols where table_name=? and column_name=?",
                Integer.class, tableName.toUpperCase(), columnName.toUpperCase());
        return count > 0;
    }

    public static IndexBuilder newIndex(String indexName, String... columns) {
        return new IndexBuilder(indexName, columns);
    }

    private static boolean isIndexExists(String tableName, String indexName, JdbcTemplate jdbcTemplate) {
        int count = jdbcTemplate.queryForObject("select count(1) from user_indexes where table_name=? and index_name=?",
                Integer.class, tableName.toUpperCase(), indexName.toUpperCase());
        return count > 0;
    }

    public static void create(Table table, JdbcTemplate jdbcTemplate) {
        TableBuilder builder = newTable(table.name);
        table.getColumns().forEach(column -> {
            builder.add(newColumn(column.getName())
                    .type(column.getType())
                    .notNull(column.isNotNull())
                    .primaryKey(column.isPrimaryKey()));
        });
        if (null != table.getIndices()) {
            table.getIndices().forEach(index -> {
                builder.add(newIndex(index.getName(), index.getColumns())
                        .unique(index.isUnique()));
            });
        }
        builder.create(jdbcTemplate);
    }

    public static class Table {
        private String name;
        private List<Column> columns;
        private List<Index> indices;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<Column> getColumns() {
            return columns;
        }

        public void setColumns(List<Column> columns) {
            this.columns = columns;
        }

        public List<Index> getIndices() {
            return indices;
        }

        public void setIndices(List<Index> indices) {
            this.indices = indices;
        }

        public void create(JdbcTemplate jdbcTemplate) {
            SchemaUtils.create(this, jdbcTemplate);
        }
    }

    private static class TableBuilder {
        private final String name;
        private List<ColumnBuilder> columnBuilderList;
        private List<IndexBuilder> indexBuilderList;

        public TableBuilder(String name) {
            this.name = name;
            this.columnBuilderList = new ArrayList<>(1);
            this.indexBuilderList = new ArrayList<>(0);
        }

        public TableBuilder add(ColumnBuilder builder) {
            columnBuilderList.add(builder);
            return this;
        }

        public TableBuilder add(IndexBuilder builder) {
            indexBuilderList.add(builder);
            return this;
        }

        public void create(JdbcTemplate jdbcTemplate) {
            if (isTableExists(name, jdbcTemplate)) {
                columnBuilderList.forEach(builder -> {
                    if (isColumnExists(name, builder.name, jdbcTemplate)) {
                        builder.modify(this, jdbcTemplate);
                    } else {
                        builder.add(this, jdbcTemplate);
                    }
                });
            } else {
                StringBuffer statement = new StringBuffer("create table ");
                statement.append(name).append(" (");
                statement.append(columnBuilderList.stream().map(ColumnBuilder::toString).collect(Collectors.joining(",")));
                statement.append(")");
                jdbcTemplate.execute(statement.toString());
            }

            indexBuilderList.forEach(builder -> {
                if (!isIndexExists(name, builder.name, jdbcTemplate)) {
                    builder.create(this, jdbcTemplate);
                }
            });
        }
    }

    private static class Column {
        private String name;
        private String type;
        private boolean notNull;
        private boolean primaryKey;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public boolean isNotNull() {
            return notNull;
        }

        public void setNotNull(boolean notNull) {
            this.notNull = notNull;
        }

        public boolean isPrimaryKey() {
            return primaryKey;
        }

        public void setPrimaryKey(boolean primaryKey) {
            this.primaryKey = primaryKey;
        }
    }

    private static class ColumnBuilder {
        private final String name;
        private String type;
        private boolean notNull;
        private boolean primaryKey;

        public ColumnBuilder(String name) {
            this.name = name;
        }

        public ColumnBuilder type(String type) {
            this.type = type;
            return this;
        }

        public ColumnBuilder notNull() {
            this.notNull = true;
            return this;
        }

        public ColumnBuilder notNull(boolean notNull) {
            this.notNull = notNull;
            return this;
        }

        public ColumnBuilder primaryKey() {
            this.primaryKey = true;
            return this;
        }

        public ColumnBuilder primaryKey(boolean primaryKey) {
            this.primaryKey = primaryKey;
            return this;
        }

        private void add(TableBuilder builder, JdbcTemplate jdbcTemplate) {
            create(builder, jdbcTemplate, false);
        }

        private void modify(TableBuilder builder, JdbcTemplate jdbcTemplate) {
            create(builder, jdbcTemplate, true);
        }

        private void create(TableBuilder builder, JdbcTemplate jdbcTemplate, boolean modify) {
            StringBuffer statement = new StringBuffer("alter table ");
            statement.append(builder.name).append(modify ? " modify " : " add ");
            statement.append(toString());
            jdbcTemplate.execute(statement.toString());
        }

        @Override
        public String toString() {
            StringBuffer str = new StringBuffer(name).append(" ");
            str.append(StringUtils.nvl(type, "varchar2(255)"));
            if (notNull) {
                str.append(" not null");
            }
            if (primaryKey) {
                str.append(" primary key");
            }
            return str.toString();
        }
    }

    private static class Index {
        private String name;
        private String[] columns;
        private boolean unique;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String[] getColumns() {
            return columns;
        }

        public void setColumns(String[] columns) {
            this.columns = columns;
        }

        public boolean isUnique() {
            return unique;
        }

        public void setUnique(boolean unique) {
            this.unique = unique;
        }
    }

    private static class IndexBuilder {
        private final String name;
        private final String[] columns;
        private boolean unique;

        public IndexBuilder(String name, String... columns) {
            this.name = name;
            this.columns = columns;
        }

        public IndexBuilder unique() {
            this.unique = true;
            return this;
        }

        public IndexBuilder unique(boolean unique) {
            this.unique = unique;
            return this;
        }

        private void create(TableBuilder builder, JdbcTemplate jdbcTemplate) {
            StringBuffer statement = new StringBuffer("create ");
            statement.append(unique ? "unique index " : "index ");
            statement.append(name).append(" on ");
            statement.append(builder.name).append(" (");
            statement.append(StringUtils.arrayToCommaDelimitedString(columns));
            statement.append(")");
            jdbcTemplate.execute(statement.toString());
        }
    }
}
