ArJdbc.load_java_part :SQLite3

require 'arjdbc/sqlite3/explain_support'
require 'arjdbc/util/table_copier'

module ArJdbc
  module SQLite3
    include Util::TableCopier

    # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
    def self.jdbc_connection_class
      ::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
    end

    def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::SQLite3Column end

    # @see ActiveRecord::ConnectionAdapters::JdbcColumn#column_types
    def self.column_selector
      [ /sqlite/i, lambda { |config, column| column.extend(Column) } ]
    end

    # @see ActiveRecord::ConnectionAdapters::JdbcColumn
    module Column

      # @override {ActiveRecord::ConnectionAdapters::JdbcColumn#init_column}
      def init_column(name, default, *args)
        if default =~ /NULL/
          @default = nil
        else
          super
        end
      end

      # @override {ActiveRecord::ConnectionAdapters::JdbcColumn#default_value}
      def default_value(value)
        # JDBC returns column default strings with actual single quotes :
        return $1 if value =~ /^'(.*)'$/

        value
      end

      # @override {ActiveRecord::ConnectionAdapters::Column#type_cast}
      def type_cast(value)
        return nil if value.nil?
        case type
        when :string then value
        when :primary_key
          value.respond_to?(:to_i) ? value.to_i : ( value ? 1 : 0 )
        when :float    then value.to_f
        when :decimal  then self.class.value_to_decimal(value)
        when :boolean  then self.class.value_to_boolean(value)
        else super
        end
      end

      private

      # @override {ActiveRecord::ConnectionAdapters::Column#simplified_type}
      def simplified_type(field_type)
        case field_type
        when /boolean/i       then :boolean
        when /text/i          then :text
        when /varchar/i       then :string
        when /int/i           then :integer
        when /float/i         then :float
        when /real|decimal/i  then
          extract_scale(field_type) == 0 ? :integer : :decimal
        when /datetime/i      then :datetime
        when /date/i          then :date
        when /time/i          then :time
        when /blob/i          then :binary
        else super
        end
      end

      # @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
      def extract_limit(sql_type)
        return nil if sql_type =~ /^(real)\(\d+/i
        super
      end

      def extract_precision(sql_type)
        case sql_type
          when /^(real)\((\d+)(,\d+)?\)/i then $2.to_i
          else super
        end
      end

      def extract_scale(sql_type)
        case sql_type
          when /^(real)\((\d+)\)/i then 0
          when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
          else super
        end
      end

    end

    # @see ActiveRecord::ConnectionAdapters::Jdbc::ArelSupport
    def self.arel_visitor_type(config = nil)
      ::Arel::Visitors::SQLite
    end

    # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#bind_substitution
    # @private
    class BindSubstitution < ::Arel::Visitors::SQLite
      include ::Arel::Visitors::BindVisitor
    end if defined? ::Arel::Visitors::BindVisitor

    ADAPTER_NAME = 'SQLite'.freeze

    def adapter_name
      ADAPTER_NAME
    end

    NATIVE_DATABASE_TYPES = {
      :primary_key => nil,
      :string => { :name => "varchar", :limit => 255 },
      :text => { :name => "text" },
      :integer => { :name => "integer" },
      :float => { :name => "float" },
      # :real => { :name=>"real" },
      :decimal => { :name => "decimal" },
      :datetime => { :name => "datetime" },
      :timestamp => { :name => "datetime" },
      :time => { :name => "time" },
      :date => { :name => "date" },
      :binary => { :name => "blob" },
      :boolean => { :name => "boolean" }
    }

    # @override
    def native_database_types
      types = NATIVE_DATABASE_TYPES.dup
      types[:primary_key] = default_primary_key_type
      types
    end

    def default_primary_key_type
      if supports_autoincrement?
        'integer PRIMARY KEY AUTOINCREMENT NOT NULL'
      else
        'integer PRIMARY KEY NOT NULL'
      end
    end

    # @override
    def supports_ddl_transactions?
      true
    end

    # @override
    def supports_savepoints?
      sqlite_version >= '3.6.8'
    end

    # @override
    def supports_partial_index?
      sqlite_version >= '3.8.0'
    end

    # @override
    def supports_add_column?
      true
    end

    # @override
    def supports_count_distinct?
      true
    end

    # @override
    def supports_autoincrement?
      true
    end

    # @override
    def supports_migrations?
      true
    end

    # @override
    def supports_primary_key?
      true
    end

    # @override
    def supports_add_column?
      true
    end

    # @override
    def supports_count_distinct?
      true
    end

    # @override
    def supports_autoincrement?
      true
    end

    # @override
    def supports_index_sort_order?
      true
    end

    # @override
    def supports_views?
      true
    end

    def sqlite_version
      @sqlite_version ||= Version.new(select_value('SELECT sqlite_version(*)'))
    end
    private :sqlite_version

    # @override
    def quote(value, column = nil)
      return value if sql_literal?(value)

      if value.kind_of?(String)
        column_type = column && column.type
        if column_type == :binary
          "x'#{value.unpack("H*")[0]}'"
        else
          super
        end
      else
        super
      end
    end

    def quote_table_name_for_assignment(table, attr)
      quote_column_name(attr)
    end if ::ActiveRecord::VERSION::MAJOR >= 4

    # @override
    def quote_column_name(name)
      %Q("#{name.to_s.gsub('"', '""')}") # "' kludge for emacs font-lock
    end

    # Quote date/time values for use in SQL input.
    # Includes microseconds if the value is a Time responding to usec.
    # @override
    def quoted_date(value)
      if value.acts_like?(:time) && value.respond_to?(:usec)
        "#{super}.#{sprintf("%06d", value.usec)}"
      else
        super
      end
    end if ::ActiveRecord::VERSION::MAJOR >= 3

    # @override
    def tables(name = nil, table_name = nil)
      sql = "SELECT name FROM sqlite_master WHERE type = 'table'"
      if table_name
        sql << " AND name = #{quote_table_name(table_name)}"
      else
        sql << " AND NOT name = 'sqlite_sequence'"
      end

      select_rows(sql, name).map { |row| row[0] }
    end

    # @override
    def table_exists?(table_name)
      table_name && tables(nil, table_name).any?
    end

    def truncate_fake(table_name, name = nil)
      execute "DELETE FROM #{quote_table_name(table_name)}; VACUUM", name
    end
    # NOTE: not part of official AR (4.2) alias truncate truncate_fake

    # Returns 62. SQLite supports index names up to 64 characters.
    # The rest is used by Rails internally to perform temporary rename operations.
    # @return [Fixnum]
    def allowed_index_name_length
      index_name_length - 2
    end

    # @override
    def create_savepoint(name = current_savepoint_name(true))
      log("SAVEPOINT #{name}", 'Savepoint') { super }
    end

    # @override
    def rollback_to_savepoint(name = current_savepoint_name(true))
      log("ROLLBACK TO SAVEPOINT #{name}", 'Savepoint') { super }
    end

    # @override
    def release_savepoint(name = current_savepoint_name(false))
      log("RELEASE SAVEPOINT #{name}", 'Savepoint') { super }
    end

    # @private
    def recreate_database(name = nil, options = {})
      drop_database(name)
      create_database(name, options)
    end

    # @private
    def create_database(name = nil, options = {})
    end

    # @private
    def drop_database(name = nil)
      tables.each { |table| drop_table(table) }
    end

    def select(sql, name = nil, binds = [])
      result = super # AR::Result (4.0) or Array (<= 3.2)
      if result.respond_to?(:columns) # 4.0
        result.columns.map! do |key| # [ [ 'id', ... ]
          key.is_a?(String) ? key.sub(/^"?\w+"?\./, '') : key
        end
      else
        result.map! do |row| # [ { 'id' => ... }, {...} ]
          record = {}
          row.each_key do |key|
            if key.is_a?(String)
              record[key.sub(/^"?\w+"?\./, '')] = row[key]
            end
          end
          record
        end
      end
      result
    end

    # @note We have an extra binds argument at the end due AR-2.3 support.
    # @override
    def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
      result = execute(sql, name, binds)
      id_value || last_inserted_id(result)
    end

    # @note Does not support prepared statements for INSERT statements.
    # @override
    def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
      # NOTE: since SQLite JDBC does not support executeUpdate but only
      # statement.execute we can not support prepared statements here :
      execute(sql, name, binds)
    end

    def table_structure(table_name)
      sql = "PRAGMA table_info(#{quote_table_name(table_name)})"
      log(sql, 'SCHEMA') { @connection.execute_query_raw(sql) }
    rescue ActiveRecord::JDBCError => error
      e = ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'")
      e.set_backtrace error.backtrace
      raise e
    end

    # @override
    def columns(table_name, name = nil)
      column = jdbc_column_class
      pass_cast_type = respond_to?(:lookup_cast_type)
      table_structure(table_name).map do |field|
        sql_type = field['type']
        if pass_cast_type
          cast_type = lookup_cast_type(sql_type)
          column.new(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'] == 0)
        else
          column.new(field['name'], field['dflt_value'], sql_type, field['notnull'] == 0)
        end
      end
    end

    # @override
    def primary_key(table_name)
      column = table_structure(table_name).find { |field| field['pk'].to_i == 1 }
      column && column['name']
    end

    # NOTE: do not override indexes without testing support for 3.7.2 & 3.8.7 !
    # @override
    def indexes(table_name, name = nil)
      # on JDBC 3.7 we'll simply do super since it can not handle "PRAGMA index_info"
      return @connection.indexes(table_name, name) if sqlite_version < '3.8' # super

      name ||= 'SCHEMA'
      exec_query_raw("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
        index_name = row['name']
        sql = "SELECT sql FROM sqlite_master"
        sql << " WHERE name=#{quote(index_name)} AND type='index'"
        sql << " UNION ALL "
        sql << "SELECT sql FROM sqlite_temp_master"
        sql << " WHERE name=#{quote(index_name)} AND type='index'"
        where = nil
        exec_query_raw(sql, name) do |index_sql|
          match = /\sWHERE\s+(.+)$/i.match(index_sql)
          where = match[1] if match
        end
        begin
          columns = exec_query_raw("PRAGMA index_info('#{index_name}')", name).map { |col| col['name'] }
        rescue => e
          # NOTE: JDBC <= 3.8.7 bug work-around :
          if e.message && e.message.index('[SQLITE_ERROR] SQL error or missing database')
            columns = []
          end
          raise e
        end
        new_index_definition(table_name, index_name, row['unique'] != 0, columns, nil, nil, where)
      end
    end

    # @override
    def remove_index!(table_name, index_name)
      execute "DROP INDEX #{quote_column_name(index_name)}"
    end

    # @override
    def rename_table(table_name, new_name)
      execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
      rename_table_indexes(table_name, new_name) if respond_to?(:rename_table_indexes) # AR-4.0 SchemaStatements
    end

    # SQLite has an additional restriction on the ALTER TABLE statement.
    # @see http://www.sqlite.org/lang_altertable.html
    def valid_alter_table_options( type, options)
      type.to_sym != :primary_key
    end

    def add_column(table_name, column_name, type, options = {})
      if supports_add_column? && valid_alter_table_options( type, options )
        super(table_name, column_name, type, options)
      else
        alter_table(table_name) do |definition|
          definition.column(column_name, type, options)
        end
      end
    end

    if ActiveRecord::VERSION::MAJOR >= 4

    # @private
    def remove_column(table_name, column_name, type = nil, options = {})
      alter_table(table_name) do |definition|
        definition.remove_column column_name
      end
    end

    else

    # @private
    def remove_column(table_name, *column_names)
      if column_names.empty?
        raise ArgumentError.new(
          "You must specify at least one column name." +
          "  Example: remove_column(:people, :first_name)"
        )
      end
      column_names.flatten.each do |column_name|
        alter_table(table_name) do |definition|
          definition.columns.delete(definition[column_name])
        end
      end
    end
    alias :remove_columns :remove_column

    end

    def change_column_default(table_name, column_name, default) #:nodoc:
      alter_table(table_name) do |definition|
        definition[column_name].default = default
      end
    end

    def change_column_null(table_name, column_name, null, default = nil)
      unless null || default.nil?
        execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
      end
      alter_table(table_name) do |definition|
        definition[column_name].null = null
      end
    end

    def change_column(table_name, column_name, type, options = {})
      alter_table(table_name) do |definition|
        include_default = options_include_default?(options)
        definition[column_name].instance_eval do
          self.type    = type
          self.limit   = options[:limit] if options.include?(:limit)
          self.default = options[:default] if include_default
          self.null    = options[:null] if options.include?(:null)
          self.precision = options[:precision] if options.include?(:precision)
          self.scale   = options[:scale] if options.include?(:scale)
        end
      end
    end

    def rename_column(table_name, column_name, new_column_name)
      unless columns(table_name).detect{|c| c.name == column_name.to_s }
        raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
      end
      alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
      rename_column_indexes(table_name, column_name, new_column_name) if respond_to?(:rename_column_indexes) # AR-4.0 SchemaStatements
    end

    # @private
    def add_lock!(sql, options)
      sql # SELECT ... FOR UPDATE is redundant since the table is locked
    end if ::ActiveRecord::VERSION::MAJOR < 3

    def empty_insert_statement_value
      # inherited (default) on 3.2 : "VALUES(DEFAULT)"
      # inherited (default) on 4.0 : "DEFAULT VALUES"
      # re-defined in native adapter on 3.2 "VALUES(NULL)"
      # on 4.0 no longer re-defined (thus inherits default)
      "DEFAULT VALUES"
    end

    def encoding
      select_value 'PRAGMA encoding'
    end

    def last_insert_id
      @connection.last_insert_rowid
    end

    protected

    def last_inserted_id(result)
      super || last_insert_id # NOTE: #last_insert_id call should not be needed
    end

    def translate_exception(exception, message)
      if msg = exception.message
        # SQLite 3.8.2 returns a newly formatted error message:
        #   UNIQUE constraint failed: *table_name*.*column_name*
        # Older versions of SQLite return:
        #   column *column_name* is not unique
        if msg.index('UNIQUE constraint failed: ') ||
           msg =~ /column(s)? .* (is|are) not unique/
          return ::ActiveRecord::RecordNotUnique.new(message, exception)
        end
      end
      super
    end

    # @private available in native adapter way back to AR-2.3
    class Version
      include Comparable

      def initialize(version_string)
        @version = version_string.split('.').map! { |v| v.to_i }
      end

      def <=>(version_string)
        @version <=> version_string.split('.').map! { |v| v.to_i }
      end

      def to_s
        @version.join('.')
      end

    end

  end
end

module ActiveRecord::ConnectionAdapters

  # NOTE: SQLite3Column exists in native adapter since AR 4.0
  remove_const(:SQLite3Column) if const_defined?(:SQLite3Column)

  class SQLite3Column < JdbcColumn
    include ArJdbc::SQLite3::Column

    def initialize(name, *args)
      if Hash === name
        super
      else
        super(nil, name, *args)
      end
    end

    def self.string_to_binary(value)
      value
    end

    def self.binary_to_string(value)
      if value.respond_to?(:encoding) && value.encoding != Encoding::ASCII_8BIT
        value = value.force_encoding(Encoding::ASCII_8BIT)
      end
      value
    end
  end

  remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)

  class SQLite3Adapter < JdbcAdapter
    include ArJdbc::SQLite3
    include ArJdbc::SQLite3::ExplainSupport

    def jdbc_connection_class(spec)
      ::ArJdbc::SQLite3.jdbc_connection_class
    end

    # @private
    Version = ArJdbc::SQLite3::Version

  end

  if ActiveRecord::VERSION::MAJOR <= 3
    remove_const(:SQLiteColumn) if const_defined?(:SQLiteColumn)
    SQLiteColumn = SQLite3Column

    remove_const(:SQLiteAdapter) if const_defined?(:SQLiteAdapter)

    SQLiteAdapter = SQLite3Adapter
  end
end