package util.getdata;

import bean.Column;
import bean.DataBase;
import bean.Schema;
import bean.Table;
import bean.View;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import util.JdbcCacheUtil;
import util.JdbcUtil;

public class CachePostgreSql extends CacheData {
   public CachePostgreSql(DataBase dataBase, CountDownLatch cdl) {
      super(dataBase, cdl);
   }

   public void run() {
      System.out.println("postgreSql开始缓存");

      try {
         this.cacheWriteToRedis();
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         try {
            JdbcCacheUtil.closeConnection(this.connection);
            if (this.statement != null) {
               this.statement.close();
            }
         } catch (SQLException throwables) {
            throwables.printStackTrace();
         }

         this.getCdl().countDown();
         System.out.println("postgreSql结束缓存");
      }

   }

   public List<Schema> cacheToMemory() throws SQLException {
      try {
         Class.forName("org.postgresql.Driver");
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }

      return this.getMeta();
   }

   public List<String> getDatabases(Statement statement) throws SQLException {
      List<String> databases = new ArrayList();
      String sql = "select datname as name from pg_database where datistemplate =false ";
      ResultSet resultSet = statement.executeQuery(sql);

      while(resultSet.next()) {
         String name = resultSet.getString("name");
         databases.add(name);
      }

      resultSet.close();
      return databases;
   }

   public List<Schema> getSchemas(Statement statement, String database) throws SQLException {
      String sql = " select t.soid as schema_id,t.nspname as SCHEMA_NAME from (\n select n.oid as soid,n.*,d.description \n from \n pg_catalog.pg_namespace n \n left outer join pg_catalog.pg_description d on d.objoid=n.oid and d.objsubid=0 and d.classoid='pg_namespace'::regclass \n where nspname not in ('information_schema','pg_catalog','pg_temp_1','pg_toast','pg_toast_temp_1')\n order by nspname \n ) t";
      List<Schema> schemas = new ArrayList();
      ResultSet resultSet = null;

      try {
         resultSet = statement.executeQuery(sql);

         while(resultSet.next()) {
            Schema schema = new Schema(resultSet.getString("SCHEMA_NAME"));
            schema.setRealName(resultSet.getString("SCHEMA_NAME"));
            schema.setDatabaseName(database);
            schemas.add(schema);
         }

         resultSet.close();
      } finally {
         if (resultSet != null) {
            resultSet.close();
         }

      }

      return schemas;
   }

   public List<Schema> getSchema(Statement statement) throws SQLException {
      List<Schema> schemas = new ArrayList();
      String sql = "select SCHEMA_NAME from information_schema.schemata";
      ResultSet resultSet = statement.executeQuery(sql);

      while(resultSet.next()) {
         Schema schema = new Schema(resultSet.getString("SCHEMA_NAME"));
         schema.setRealName(resultSet.getString("SCHEMA_NAME"));
         schemas.add(schema);
      }

      resultSet.close();
      return schemas;
   }

   public List<Table> getTable(Statement statement, String schema) throws SQLException {
      List<Table> tables = new ArrayList();
      String sql = "select TABLE_NAME from information_schema.tables where TABLE_TYPE = 'BASE TABLE' AND table_schema='" + schema + "'";
      ResultSet resultSet = statement.executeQuery(sql);

      while(resultSet.next()) {
         Table table = new Table(resultSet.getString("TABLE_NAME"), schema);
         table.setRealName(resultSet.getString("TABLE_NAME"));
         tables.add(table);
      }

      resultSet.close();
      return tables;
   }

   public List<Column> getColumn(Statement statement, String schema, String table) throws SQLException {
      List<Column> columns = new ArrayList();
      String sql = "select COLUMN_NAME,(case DATA_TYPE when 'character varying' then 'varchar' else DATA_TYPE end ) as DATA_TYPE,case  \n                when data_type = 'longtext' or data_type = 'datetime' or data_type = 'clob' or data_type = 'date' or data_type = 'time' or data_type = 'timestamp' then '' \n                when CHARACTER_MAXIMUM_LENGTH is not null then ''||CHARACTER_MAXIMUM_LENGTH \n                when (NUMERIC_PRECISION is not null and numeric_scale is not null and numeric_scale=0)then ''||NUMERIC_PRECISION \n                when (NUMERIC_PRECISION is not null and numeric_scale is not null)then ''||NUMERIC_PRECISION||','||numeric_scale \n                when (NUMERIC_PRECISION is not null)then ''||NUMERIC_PRECISION \n                end as COLUMN_LENGTH  from information_schema.columns where TABLE_SCHEMA = '" + schema + "' and TABLE_NAME = '" + table + "'" + " order by table_schema ,table_name,ordinal_position ";
      ResultSet resultSet = statement.executeQuery(sql);

      while(resultSet.next()) {
         Column column = new Column(resultSet.getString("COLUMN_NAME"), schema, table, resultSet.getString("DATA_TYPE"));
         column.setColumnType(column.getDataType());
         column.setLength(resultSet.getString("COLUMN_LENGTH"));
         columns.add(column);
      }

      resultSet.close();
      return columns;
   }

   public List<View> getView(Statement statement, String schema) throws SQLException {
      List<View> views = new ArrayList();
      String sql = "select TABLE_NAME,VIEW_DEFINITION from information_schema.VIEWS where TABLE_SCHEMA = '" + schema + "'";
      ResultSet resultSet = statement.executeQuery(sql);

      while(resultSet.next()) {
         String table_name = resultSet.getString("TABLE_NAME");
         String view_definition = resultSet.getString("VIEW_DEFINITION").replaceAll("(?i)[():]|text", "");
         views.add(new View(table_name, view_definition));
      }

      resultSet.close();
      return views;
   }

   public List<Schema> getMeta() throws SQLException {
      List<String> databases = null;

      try {
         this.connection = JdbcUtil.getConnection(this.DataBase.getUrl(), this.DataBase.getUsername(), this.DataBase.getPassword());
         this.statement = this.connection.createStatement();
          databases = this.getDatabases(this.statement);
      } finally {
         if (this.statement != null) {
            this.statement.close();
         }

         if (this.connection != null) {
            this.connection.close();
         }

      }

      if (databases == null || ((List)databases).isEmpty()) {
          databases = new ArrayList();
      }

      List<Schema> schemaList = new ArrayList();

      for(String database : databases) {
         if (database != null && !database.isEmpty()) {
            Statement st = null;

            try {
               String url = this.getUrl(this.DataBase.getUrl(), database);
               this.connection = JdbcUtil.getConnection(url, this.DataBase.getUsername(), this.DataBase.getPassword());
               st = this.connection.createStatement();
               Map<String, Schema> schemaMap = new HashMap();
               List<Schema> schemas = this.getSchemas(st, database, schemaMap);
               this.getViews(st, database, schemaMap);
               Map<String, Table> tableMap = new HashMap();
               List<Table> tables = this.getTables(st, database, schemaMap, tableMap);
               if (tables != null && tables.size() > 0) {
                  int SPLIT = 200;
                  List<TableItem> items = new ArrayList();
                  List<List<TableItem>> groups = new ArrayList();
                  int k = 0;
                  String dbName = "";

                  for(Table table : tables) {
                     ++k;
                     if (dbName.isEmpty()) {
                        dbName = table.getDbName();
                     }

                     if (!dbName.equals(table.getDbName())) {
                        groups.add(items);
                        items = new ArrayList();
                        dbName = table.getDbName();
                        k = 0;
                     }

                     TableItem item = new TableItem();
                     item.table = table;
                     item.databaseName = table.getDbName();
                     item.tableName = table.getName();
                     item.schemaName = table.getSchema();
                     items.add(item);
                     if (k % 200 == 0) {
                        groups.add(items);
                        items = new ArrayList();
                     }
                  }

                  if (!items.isEmpty()) {
                     groups.add(items);
                  }

                  CountDownLatch latch = new CountDownLatch(groups.size());

                  for(List<TableItem> group : groups) {
                     ColumnThread columnsGetter = new ColumnThread(this.connection, latch, group);
                     Thread thread = new Thread(columnsGetter);
                     thread.start();
                  }

                  try {
                     latch.await();
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
               }

               schemaList.addAll(schemas);
            } finally {
               if (st != null) {
                  try {
                     st.close();
                  } catch (Exception e) {
                     e.printStackTrace();
                  }
               }

               if (this.connection != null) {
                  try {
                     this.connection.close();
                  } catch (Exception e) {
                     e.printStackTrace();
                  }
               }

            }
         }
      }

      return schemaList;
   }

   public List<View> getViews(Statement statement, String dbname, Map<String, Schema> schemas) throws SQLException {
      List<View> views = new ArrayList();
      StringBuilder sql = new StringBuilder();
      sql.append("select * from pg_views where schemaname not in ('pg_catalog','pg_temp_1','pg_toast','pg_toast_temp_1','information_schema') ");
      ResultSet resultSet = null;

      try {
         resultSet = statement.executeQuery(sql.toString());

         while(resultSet.next()) {
            String table_name = "";
            if (resultSet.getString("definition") != null) {
               String view_definition = resultSet.getString("definition").replace("`", "");
               String sch = resultSet.getString("schemaname");
               String viewName = resultSet.getString("viewname");
               View view = new View(viewName, view_definition);
               view.setSchema(sch);
               view.setDbname(dbname);
               views.add(view);
               String schemaId = dbname + "." + sch;
               Schema schema = (Schema)schemas.get(schemaId);
               if (schema != null) {
                  schema.getViewList().add(view);
               }
            }
         }
      } finally {
         if (resultSet != null) {
            resultSet.close();
         }

      }

      return views;
   }

   protected String getUrl(String url, String db) {
      if (db != null && db.length() != 0) {
         int i = url.indexOf("?");
         if (i == -1) {
            int i1 = url.lastIndexOf("/");
            if (i1 == -1) {
               url = url + "/" + db;
            } else {
               String prefix = url.substring(0, i1 + 1);
               url = prefix + db;
            }
         } else {
            int i1 = url.lastIndexOf("/");
            if (i1 == -1) {
               String prefix = url.substring(0, i);
               String afterix = url.substring(i);
               url = prefix + db + afterix;
            } else {
               String prefix = url.substring(0, i1 + 1);
               String afterix = url.substring(i);
               url = prefix + db + afterix;
            }
         }

         return url;
      } else {
         return url;
      }
   }

   public List<Schema> getSchemas(Statement statement, String databaseName, Map<String, Schema> outMap) throws SQLException {
      StringBuilder sql = new StringBuilder();
      List<Schema> schemas = new ArrayList();
      sql.append("SELECT e.nspname as TABLE_CATALOG FROM  pg_namespace e  WHERE  nspname not in ('pg_catalog','pg_temp_1','pg_toast','pg_toast_temp_1','information_schema')  ");
      ResultSet resultSet = null;

      try {
         resultSet = statement.executeQuery(sql.toString());

         while(resultSet.next()) {
            Schema schema = new Schema(resultSet.getString("TABLE_CATALOG"));
            schema.setRealName(resultSet.getString("TABLE_CATALOG"));
            schema.setDatabaseName(databaseName);
            schemas.add(schema);
            String id = databaseName + "." + schema.getName();
            outMap.put(id, schema);
         }
      } finally {
         if (resultSet != null) {
            resultSet.close();
         }

      }

      return schemas;
   }

   public List<Table> getTables(Statement statement, String databaseName, Map<String, Schema> schemas, Map<String, Table> outMap) throws SQLException {
      StringBuilder sql = new StringBuilder();
      sql.append("SELECT e.nspname as TABLE_SCHEMA,c.relname as TABLE_NAME, d.description AS REMARKS \nFROM  pg_class c \ninner join pg_namespace e on e.oid=c.relnamespace LEFT JOIN pg_description d ON (c.oid = d.objoid AND d.objsubid = 0)  \nWHERE \n  nspname not in ('pg_catalog','pg_temp_1','pg_toast','pg_toast_temp_1','information_schema')\nand c.relkind in ('r','f') ");
      List<Table> tables = new ArrayList();
      ResultSet resultSet = null;

      try {
         resultSet = statement.executeQuery(sql.toString());

         while(resultSet.next()) {
            String schemaName = resultSet.getString("TABLE_SCHEMA");
            String tableName = resultSet.getString("TABLE_NAME");
            String remarks = resultSet.getString("REMARKS");
            Table table = new Table(tableName, schemaName);
            table.setSchema(schemaName);
            table.setDbName(databaseName);
            table.setRealName(tableName);
            tables.add(table);
            String schId = databaseName + "." + schemaName;
            Schema schema = (Schema)schemas.get(schId);
            if (schema != null) {
               schema.getTableList().add(table);
            }

            String tbId = databaseName + "." + schemaName + "." + tableName;
            outMap.put(tbId, table);
         }
      } finally {
         if (resultSet != null) {
            resultSet.close();
         }

      }

      return tables;
   }

   class ColumnThread implements Runnable {
      private final Connection connection;
      CountDownLatch latch;
      List<TableItem> tables;
      List<Column> columns;

      public ColumnThread(Connection connection, CountDownLatch latch, List<TableItem> tables) {
         this.connection = connection;
         this.tables = tables;
         this.latch = latch;
         this.columns = new ArrayList();
      }

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

      public void run() {
         try {
            Map<String, List<TableItem>> dbTablesMap = new HashMap();
            Map<String, TableItem> tbMap = new HashMap();

            for(TableItem table : this.tables) {
               String tbId = table.databaseName + "." + table.schemaName + "." + table.tableName;
               tbMap.put(tbId, table);
               List<TableItem> lstTable = (List)dbTablesMap.get(table.databaseName);
               if (lstTable == null) {
                  dbTablesMap.put(table.databaseName, new ArrayList());
                  lstTable = (List)dbTablesMap.get(table.databaseName);
               }

               lstTable.add(table);
            }

            StringBuilder sql = new StringBuilder();
            Set<Map.Entry<String, List<TableItem>>> entries = dbTablesMap.entrySet();
            int size = entries.size();
            int j = 0;

            for(Map.Entry<String, List<TableItem>> entry : entries) {
               String dbname = (String)entry.getKey();
               List<TableItem> tables = (List)entry.getValue();
               sql.append("SELECT '" + dbname + "' as TABLE_CATALOG, e.nspname as TABLE_SCHEMA," + "c.relname as TABLE_NAME,a.attname as COLUMN_NAME," + "f.typname as DATA_TYPE \n" + ",(case a.atttypid \n" + "\twhen 16 then 1 \n" + "\twhen 17 then 2147483647 \n" + "\twhen 25 then 2147483647 \n" + "\twhen 18 then 1 \n" + "\twhen 20 then 20 \n" + "\twhen 21 then 6 \n" + "\twhen 23 then 11 \n" + "\twhen 26 then 10 \n" + "\twhen 700 then 15 \n" + "\twhen 701 then 25 \n" + "\twhen 1042 then (case when atttypmod=-1 then 2147483647 else atttypmod-4 end) \n" + "\twhen 1043 then (case when atttypmod=-1 then 2147483647 else atttypmod-4 end) \n" + "\twhen 1082 then 13\n" + "\twhen 1083 then 8 + (case atttypmod when -1 then 7 when 0 then 0 when 1 then 3 else atttypmod +1  end)\n" + "\twhen 1114 then 22 + (case atttypmod when -1 then 7 when 0 then 0 when 1 then 3 else atttypmod +1  end)\n" + "\twhen 1184 then 28 + (case atttypmod when -1 then 7 when 0 then 0 when 1 then 3 else atttypmod +1  end)\n" + "\twhen 1266 then 14 + (case atttypmod when -1 then 7 when 0 then 0 when 1 then 3 else atttypmod +1  end)\n" + "\twhen 1186 then 49 \n" + "\twhen 1560 then atttypmod \n" + "\twhen 1562 then (case atttypmod when -1 then 2147483647 else atttypmod  end)  \n" + "\twhen 1700 then (case atttypmod when -1 then 131089 else (1+ (atttypmod - 4 >> 16 & 65535) + (case when (atttypmod - 4 & 65535)=0 then 0 else 1 end) )  end)   \n" + "\telse f.typlen\n" + "\tend\n" + "\t) as LEN" + ",(case a.atttypid  \n" + "\twhen  700 then 8\n" + "\twhen  701 then 17\n" + "\twhen  1083 then (case atttypmod when -1 then 6 else  atttypmod end)\n" + "\twhen  1114 then (case atttypmod when -1 then 6 else  atttypmod end)\n" + "\twhen  1184 then (case atttypmod when -1 then 6 else  atttypmod end)\n" + "\twhen  1266 then (case atttypmod when -1 then 6 else  atttypmod end)\n" + "\twhen  1186 then (case atttypmod when -1 then 6 else  (atttypmod&65535) end)\n" + "\twhen  1700 then (case atttypmod when -1 then 0 else  ((atttypmod-4)&65535) end)\n" + "\telse 0 \n" + "\tend) as SCALE" + ",pg_catalog.pg_get_expr(ad.adbin, ad.adrelid, true) as def_value\n" + ",dsc.description as DESCRIPTION," + "" + "(\tselect count(*) from (\n" + "\t\n" + "\tselect * from (\n" + "\tselect  a.attname as COL_NAME ,e.conname ,f.nspname as TABLE_SCHEMA,p.relname as TABLE_NAME\n" + "\tfrom pg_catalog.pg_constraint e\n" + "\tinner join pg_class p on e.conrelid  =p.oid\n" + "\tinner join pg_catalog.pg_attribute  a on a.attrelid = p.oid and a.attnum =e.conkey [1]\n" + "\tinner join pg_namespace f on f.oid=p.relnamespace\n" + "\tand e.contype ='p'\n" + "\t) tt where tt.table_schema =e.nspname  and tt.TABLE_NAME=c.relname and tt.col_name=a.attname \n" + "\t)t) as PK" + "" + " \n" + "FROM pg_attribute a\n" + "INNER JOIN pg_class c ON (a.attrelid=c.oid)\n" + "inner join pg_namespace e on e.oid=c.relnamespace \n" + "LEFT OUTER JOIN pg_attrdef ad ON (a.attrelid=ad.adrelid AND a.attnum = ad.adnum)\n" + "LEFT OUTER JOIN pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid)\n" + "left join pg_type f on a.atttypid = f.oid\n" + "WHERE NOT a.attisdropped \n" + "and c.relkind in ('r','f')\n" + " and  e.nspname not in ('pg_catalog','pg_temp_1','pg_toast','pg_toast_temp_1','information_schema') " + "and a.attnum > 0\n" + "and exists (");

               for(int k = 0; k < tables.size(); ++k) {
                  TableItem tableItem = (TableItem)tables.get(k);
                  String dbName = tableItem.databaseName;
                  String tbName = tableItem.tableName;
                  String sch = tableItem.schemaName;
                  sql.append(" SELECT 1 FROM  pg_class c1  inner join pg_namespace e1 on e.oid=c.relnamespace  WHERE  c1.oid=c.oid and e1.oid=e.oid and e1.nspname ='" + sch + "' and c.relname='" + tbName + "' and c.relkind in ('r','f') ");
                  if (k != tables.size() - 1) {
                     sql.append(" \n UNION ALL ");
                  }
               }

               sql.append(")");
               sql.append(" order by a.attnum ");
               if (j != size - 1) {
                  sql.append("\n union all \n");
               }

               ++j;
            }

            try {
               Statement statement = this.connection.createStatement();

               Column column;
               for(ResultSet resultSet = statement.executeQuery(sql.toString()); resultSet.next(); this.columns.add(column)) {
                  String dbName = resultSet.getString("TABLE_CATALOG");
                  String schema = resultSet.getString("TABLE_SCHEMA");
                  String table = resultSet.getString("TABLE_NAME");
                  String columnName = resultSet.getString("COLUMN_NAME");
                  column = new Column(columnName, schema, table, resultSet.getString("DATA_TYPE"));
                  column.setDbname(dbName);
                  int character_maximum_length = resultSet.getInt("LEN");
                  if (character_maximum_length > 0) {
                     column.setLength(String.valueOf(character_maximum_length));
                  }

                  Object scale = resultSet.getObject("SCALE");
                  column.setDigits(scale == null ? "" : scale.toString());
                  column.setSchema(schema);
                  column.setColumnType(column.getDataType());
                  column.setRemark(resultSet.getString("DESCRIPTION"));
                  column.setPk(resultSet.getInt("PK") > 0 ? 1 : 0);
                  String tbId = dbName + "." + schema + "." + table;
                  TableItem item = (TableItem)tbMap.get(tbId);
                  if (item != null) {
                     item.table.getColumnList().add(column);
                  } else {
                     System.out.println(tbId);
                  }
               }
            } catch (SQLException throwables) {
               throwables.printStackTrace();
            }
         } finally {
            if (this.latch != null) {
               try {
                  this.latch.countDown();
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }

         }

      }
   }
}
