/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hive.hplsql;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.HashMap;

import org.antlr.v4.runtime.ParserRuleContext;

/**
 * Metadata
 */
public class Meta {
    
  HashMap<String, HashMap<String, Row>> dataTypes = new HashMap<String, HashMap<String, Row>>();
  
  Exec exec;
  boolean trace = false;  
  boolean info = false;
  
  Meta(Exec e) {
    exec = e;  
    trace = exec.getTrace();
    info = exec.getInfo();
  }
  
  /**
   * Get the data type of column (column name is qualified i.e. schema.table.column)
   */
  String getDataType(ParserRuleContext ctx, String conn, String column) {
    String type = null;
    HashMap<String, Row> map = dataTypes.get(conn);
    if (map == null) {
      map = new HashMap<String, Row>();
      dataTypes.put(conn, map);
    }
    ArrayList<String> twoparts = splitIdentifierToTwoParts(column);
    if (twoparts != null) {
      String tab = twoparts.get(0);
      String col = twoparts.get(1).toUpperCase();
      Row row = map.get(tab);
      if (row != null) {
        type = row.getType(col);
      }
      else {
        row = readColumns(ctx, conn, tab, map);
        if (row != null) {
          type = row.getType(col);
        }
      }
    }
    return type;
  }
  
  /**
   * Get data types for all columns of the table
   */
  Row getRowDataType(ParserRuleContext ctx, String conn, String table) {
    HashMap<String, Row> map = dataTypes.get(conn);
    if (map == null) {
      map = new HashMap<String, Row>();
      dataTypes.put(conn, map);
    }
    Row row = map.get(table);
    if (row == null) {
      row = readColumns(ctx, conn, table, map);
    }
    return row;
  }
  
  /**
   * Read the column data from the database and cache it
   */
  Row readColumns(ParserRuleContext ctx, String conn, String table, HashMap<String, Row> map) {
    Row row = null;
    Conn.Type connType = exec.getConnectionType(conn); 
    if (connType == Conn.Type.HIVE) {
      String sql = "DESCRIBE " + table;
      Query query = new Query(sql);
      exec.executeQuery(ctx, query, conn); 
      if (!query.error()) {
        ResultSet rs = query.getResultSet();
        try {
          while (rs.next()) {
            String col = rs.getString(1);
            String typ = rs.getString(2);
            if (row == null) {
              row = new Row();
            }
            row.addColumn(col.toUpperCase(), typ);
          } 
          map.put(table, row);
        } 
        catch (Exception e) {}
      }
      exec.closeQuery(query, conn);
    }
    else {
      Query query = exec.prepareQuery(ctx, "SELECT * FROM " + table, conn); 
      if (!query.error()) {
        try {
          PreparedStatement stmt = query.getPreparedStatement();
          ResultSetMetaData rm = stmt.getMetaData();
          int cols = rm.getColumnCount();
          for (int i = 1; i <= cols; i++) {
            String col = rm.getColumnName(i);
            String typ = rm.getColumnTypeName(i);
            if (row == null) {
              row = new Row();
            }
            row.addColumn(col.toUpperCase(), typ);
          }
          map.put(table, row);
        }
        catch (Exception e) {}
      }
      exec.closeQuery(query, conn);
    }
    return row;
  }
  
  /**
   * Normalize identifier for a database object (convert "" [] to `` i.e.)
   */
  public String normalizeObjectIdentifier(String name) {
    ArrayList<String> parts = splitIdentifier(name);
    if (parts != null) {  // more then one part exist
      StringBuilder norm = new StringBuilder();
      int size = parts.size();
      boolean appended = false;
      for (int i = 0; i < size; i++) {
        if (i == size - 2) {   // schema name
          String schema = getTargetSchemaName(parts.get(i));
          if (schema != null) {
            norm.append(schema);
            appended = true;
          }          
        } else {
          norm.append(normalizeIdentifierPart(parts.get(i)));
          appended = true;
        }
        if (i + 1 < parts.size() && appended) {
          norm.append(".");
        }
      }
      return norm.toString();
    }
    return normalizeIdentifierPart(name);
  }
  
  /**
   * Get the schema name to be used in the final executed SQL
   */
  String getTargetSchemaName(String name) {
    if (name.equalsIgnoreCase("dbo") || name.equalsIgnoreCase("[dbo]")) {
      return null;
    }
    return normalizeIdentifierPart(name);
  }  
  
  /**
   * Normalize identifier (single part) - convert "" [] to `` i.e.
   */
  public String normalizeIdentifierPart(String name) {
    char start = name.charAt(0);
    char end = name.charAt(name.length() - 1);
    if ((start == '[' && end == ']') || (start == '"' && end == '"')) {
      return '`' + name.substring(1, name.length() - 1) + '`'; 
    }
    return name;
  }
  
  /**
   * Split qualified object to 2 parts: schema.tab.col -> schema.tab|col; tab.col -> tab|col 
   */
  public ArrayList<String> splitIdentifierToTwoParts(String name) {
    ArrayList<String> parts = splitIdentifier(name);    
    ArrayList<String> twoparts = null;
    if (parts != null) {
      StringBuilder id = new StringBuilder();
      int i = 0;
      for (; i < parts.size() - 1; i++) {
        id.append(parts.get(i));
        if (i + 1 < parts.size() - 1) {
          id.append(".");
        }
      }
      twoparts = new ArrayList<String>();
      twoparts.add(id.toString());
      id.setLength(0);
      id.append(parts.get(i));
      twoparts.add(id.toString());
    }
    return twoparts;
  }
  
  /**
   * Split identifier to parts (schema, table, colum name etc.)
   * @return null if identifier contains single part
   */
  public ArrayList<String> splitIdentifier(String name) {
    ArrayList<String> parts = null;
    int start = 0;
    for (int i = 0; i < name.length(); i++) {
      char c = name.charAt(i);
      char del = '\0';
      if (c == '`' || c == '"') {
        del = c;        
      }
      else if (c == '[') {
        del = ']';
      }
      if (del != '\0') {
        for (int j = i + 1; i < name.length(); j++) {
          i++;
          if (name.charAt(j) == del) {
            break;
          }
        }
        continue;
      }
      if (c == '.') {
        if (parts == null) {
          parts = new ArrayList<String>();
        }
        parts.add(name.substring(start, i));
        start = i + 1;
      }
    }
    if (parts != null) {
      parts.add(name.substring(start));
    }
    return parts;
  }
}



