require 'data_mapper/adapters/data_object_adapter'
begin
  gem 'do_postgres', '<=0.2.4'
  require 'do_postgres'
rescue LoadError
  STDERR.puts <<-EOS
You must install the DataObjects::Postgres driver.
  gem install do_postgres
EOS
  exit
end

module DataMapper
  module Adapters
    
    class PostgresqlAdapter < DataObjectAdapter
      
      def schema_search_path
        @schema_search_path || @schema_search_path = begin
          if @configuration.schema_search_path
            @configuration.schema_search_path.split(',').map do |part|
              part.blank? ? nil : part.strip.ensure_wrapped_with("'")
            end.compact
          else
            []
          end
        end
      end
      
      def create_connection
        connection_string = ""
        builder = lambda do |k,v|
          connection_string << "#{k}=#{@configuration.send(v)} " unless
            @configuration.send(v).blank?
        end
        builder['host', :host]
        builder['user', :username]
        builder['password', :password]
        builder['dbname', :database]
        builder['socket', :socket]
        builder['port', :port]
        conn = DataObject::Postgres::Connection.new(connection_string.strip)
        conn.logger = self.logger
        conn.open

        unless schema_search_path.empty?
          execute("SET search_path TO #{schema_search_path}")
        end

        return conn
      end
      
      def database_column_name
        "TABLE_CATALOG"
      end
            
      TABLE_QUOTING_CHARACTER = '"'.freeze
      COLUMN_QUOTING_CHARACTER = '"'.freeze
      
      TYPES.merge!({
        :integer => 'integer'.freeze,
        :datetime => 'timestamp with time zone'.freeze
      })
        
      module Mappings
        class Table
          def sequence_sql
            @sequence_sql ||= quote_table("_id_seq").freeze
          end
          
          def to_create_table_sql
            schema_name = name.index('.') ? name.split('.').first : nil
            schema_list = @adapter.query('SELECT nspname FROM pg_namespace').join(',')
          
            sql = if schema_name and !schema_list.include?(schema_name)
                "CREATE SCHEMA #{@adapter.quote_table_name(schema_name)}; " 
            else
              ''
            end
            
            sql << "CREATE TABLE " << to_sql
          
            sql << " (" << columns.map do |column|
              column.to_long_form
            end.join(', ') << ")"
          
            return sql
          end
          
          # The logic of this comes from AR; it was modified for smarter typecasting
          def unquote_default(default)
            # Boolean types
            return true if default =~ /true/i
            return false if default =~ /false/i

            # Char/String/Bytea type values
            return $1 if default =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/

            # Numeric values
            return value.to_f if default =~ /^-?[0-9]+(\.[0-9]*)/
            return value.to_i if default =~ /^-?[0-9]+/

            # Fixed dates / times
            return Date.parse($1) if default =~ /^'(.+)'::date/
            return DateTime.parse($1) if default =~ /^'(.+)'::timestamp/            

            # Anything else is blank, some user type, or some function
            # and we can't know the value of that, so return nil.
            return nil       
          end
          
          # def to_exists_sql
          #   @to_exists_sql || @to_exists_sql = <<-EOS.compress_lines
          #     SELECT TABLE_NAME
          #     FROM INFORMATION_SCHEMA.TABLES
          #     WHERE TABLE_NAME = ?
          #       AND TABLE_CATALOG = ?
          #   EOS
          # end
          # 
          # def to_column_exists_sql
          #   @to_column_exists_sql || @to_column_exists_sql = <<-EOS.compress_lines
          #     SELECT TABLE_NAME, COLUMN_NAME
          #     FROM INFORMATION_SCHEMA.COLUMNS
          #     WHERE TABLE_NAME = ?
          #     AND COLUMN_NAME = ?
          #     AND TABLE_CATALOG = ?
          #   EOS
          # end          
                    
          private 
          
          def quote_table(table_suffix = nil)
            parts = name.split('.')
            parts.last << table_suffix if table_suffix
            parts.map { |part|
              @adapter.quote_table_name(part) }.join('.')
          end
        end # class Table
        
        class Schema
          
          def database_tables
            get_database_tables("public")
          end
          
        end
        
        class Column
          def serial_declaration
            "SERIAL"
          end
          
          def check_declaration
          	"CHECK (" << check << ")"
          end
          
          def to_alter_sql
            "ALTER TABLE " <<  table.to_sql << " ALTER COLUMN " << to_alter_form
          end
          
          def alter!
            result = super
            reset_alter_state!
            result
          end
          
          def reset_alter_state!
            @type_changed = false
            @default_changed = false
          end
          
          def default=(value)
            @default_changed = true
            super
          end
          
          def type=(value)
            @type_changed = true
            super
          end
          
          def to_alter_form
            sql = to_sql.dup
            
            changes = 0
            
            if @type_changed
              changes += 1
              sql << " TYPE " << type_declaration
              case self.type
              when :integer then sql << " USING #{to_sql}::integer"
              when :datetime then
                sql << " USING timestamp with time zone"
              end
            end
            
            if @default_changed
              sql << ", " if changes += 1 > 1
              
              if default.blank? || default_declaration.blank?
                sql << " DROP DEFAULT"
              else
                sql << " " << default_declaration
              end
            end
            
            sql
          end
          
          def to_long_form
            @to_long_form || begin
              @to_long_form = "#{to_sql}"
              
              if serial? && !serial_declaration.blank?
                @to_long_form << " #{serial_declaration}"
                if key? && !primary_key_declaration.blank?
                  @to_long_form << " #{primary_key_declaration}"
                end
              else
                @to_long_form << " #{type_declaration}"
                
                unless nullable? || not_null_declaration.blank?
                  @to_long_form << " #{not_null_declaration}"
                end
                
                if default && !default_declaration.blank?
                  @to_long_form << " #{default_declaration}"
                end
                
                if unique? && !unique_declaration.blank?
                  @to_long_form << " #{unique_declaration}"
                end
                
                if check && !check_declaration.blank?
                  @to_long_form << " #{check_declaration}"
                end
              end
                      
              @to_long_form
            end
          end
          
          # size is still required, as length in postgres behaves slightly differently
          def size
            case self.type
            #strings in postgres can be unlimited length
            when :string then return (@options.has_key?(:length) || @options.has_key?(:size) ? @size : nil)
            else nil
            end
          end
        end # class Column
      end # module Mappings
      
    end # class PostgresqlAdapter
    
  end # module Adapters
end # module DataMapper
