module Swift
  class Adapter
    attr_reader :options

    def get scheme, keys
      relation = scheme.new(keys)
      prepare_get(scheme).execute(*relation.tuple.values_at(*scheme.header.keys)).first
    end

    def all scheme, conditions = '', *binds, &block
      where = "where #{exchange_names(scheme, conditions)}" unless conditions.empty?
      prepare(scheme, "select * from #{scheme.store} #{where}").execute(*binds, &block)
    end

    def first scheme, conditions = '', *binds, &block
      where = "where #{exchange_names(scheme, conditions)}" unless conditions.empty?
      prepare(scheme, "select * from #{scheme.store} #{where} limit 1").execute(*binds, &block).first
    end

    def create scheme, *relations
      statement = prepare_create(scheme)
      relations.map do |relation|
        relation = scheme.new(relation) unless relation.kind_of?(scheme)
        if statement.execute(*relation.tuple.values_at(*scheme.header.insertable)) && scheme.header.serial
          relation.tuple[scheme.header.serial] = statement.insert_id
        end
        relation
      end
    end

    def update scheme, *relations
      statement = prepare_update(scheme)
      relations.map do |relation|
        relation = scheme.new(relation) unless relation.kind_of?(scheme)
        statement.execute(*relation.tuple.values_at(*scheme.header.updatable, *scheme.header.keys))
        relation
      end
    end

    def destroy scheme, *relations
      statement = prepare_destroy(scheme)
      relations.map do |relation|
        relation = scheme.new(relation) unless relation.kind_of?(scheme)
        if result = statement.execute(*relation.tuple.values_at(*scheme.header.keys))
          relation.freeze
        end
        result
      end
    end

    def migrate! scheme
      keys   =  scheme.header.keys
      fields =  scheme.header.map{|p| field_definition(p)}.join(', ')
      fields += ", primary key (#{keys.join(', ')})" unless keys.empty?

      execute("drop table if exists #{scheme.store}")
      execute("create table #{scheme.store} (#{fields})")
    end

    protected
      def exchange_names scheme, query
        query.gsub(/:(\w+)/){ scheme.send($1.to_sym).field }
      end

      def returning?
        raise NotImplementedError
      end

      def prepare_cached scheme, name, &block
        @prepared               ||= Hash.new{|h,k| h[k] = Hash.new} # Autovivification please Matz!
        @prepared[scheme][name] ||= prepare(scheme, yield)
      end

      def prepare_get scheme
        prepare_cached(scheme, :get) do
          where = scheme.header.keys.map{|key| "#{key} = ?"}.join(' and ')
          "select * from #{scheme.store} where #{where} limit 1"
        end
      end

      def prepare_create scheme
        prepare_cached(scheme, :create) do
          values    = (['?'] * scheme.header.insertable.size).join(', ')
          returning = "returning #{scheme.header.serial}" if scheme.header.serial and returning?
          "insert into #{scheme.store} (#{scheme.header.insertable.join(', ')}) values (#{values}) #{returning}"
        end
      end

      def prepare_update scheme
        prepare_cached(scheme, :update) do
          set   = scheme.header.updatable.map{|field| "#{field} = ?"}.join(', ')
          where = scheme.header.keys.map{|key| "#{key} = ?"}.join(' and ')
          "update #{scheme.store} set #{set} where #{where}"
        end
      end

      def prepare_destroy scheme
        prepare_cached(scheme, :destroy) do
          where = scheme.header.keys.map{|key| "#{key} = ?"}.join(' and ')
          "delete from #{scheme.store} where #{where}"
        end
      end

      def field_definition attribute
        "#{attribute.field} " + case attribute
          when Type::String     then 'text'
          when Type::Integer    then attribute.serial ? 'serial' : 'integer'
          when Type::Float      then 'float'
          when Type::BigDecimal then 'numeric'
          when Type::Time       then 'timestamp'
          when Type::Boolean    then 'boolean'
          when Type::IO         then 'blob'
          else 'text'
        end
      end
  end # Adapter
end # Swift