# A few pure functions for converting between volt types/options and sequel
# type/options

module Volt
  module Sql
    module Helper


      # This method takes in info from the db schema and returns the volt field
      # klass and options that would have been used to create it.
      #
      # @returns [Array of klasses, Hash of options]
      def self.klasses_and_options_from_db(db_field)
        klasses = []
        options = {}

        # merge values based on map (options key from db_key)
        {
          :text => :text,
          :size => :max_length,
          :nil  => :allow_null,
          :default => :ruby_default
        }.each_pair do |opts_key, db_key|
          options[opts_key] = db_field[db_key] if db_field.has_key?(db_key)
        end

        options.delete(:default) if options[:default] == nil

        db_type = db_field[:db_type].to_sym

        case db_field[:type]
        when :string
          klasses << String
        when :datetime
          klasses << VoltTime
        when :boolean
          klasses << Volt::Boolean
        when :float
          klasses << Float
        else
          case db_type
          when :text
          when :string
            klasses << String
          when :numeric
            klasses << Numeric
          when :integer
            klasses << Fixnum
          end
        end

        if klasses.size == 0
          raise "Could not match database type #{db_type} in #{db_field.inspect}"
        end

        # Default is to allow nil
        unless options[:nil] == false
          klasses << NilClass
        end
        options.delete(:nil)

        return klasses, options
      end


      # Takes in the klass and options specified on the model or an add_column
      # and returns the correct klass/options for add_column in sequel.
      def self.column_type_and_options_for_sequel(klasses, options)
        options = options.dup

        # Remove from start fields
        klasses ||= [String, NilClass]

        allow_nil = klasses.include?(NilClass)
        klasses = klasses.reject {|klass| klass == NilClass }

        if klasses.size > 1
          raise MultiTypeException, 'the sql adaptor only supports a single type (or NilClass) for each field.'
        end

        klass = klasses.first

        if options.has_key?(:nil)
          options[:allow_null] = options.delete(:nil)
        else
          options[:allow_null] = allow_nil
        end

        if klass == String
          # If no length restriction, make it text
          if options[:size]
            if options[:size] > 255
              # Make the field text
              options.delete(:size)
              options[:text] = true
            end
          else
            options[:text] = true
          end
        elsif klass == VoltTime
          klass = Time
        elsif klass == Volt::Boolean || klass == TrueClass || klass == FalseClass
          klass = TrueClass # what sequel uses for booleans
        end

        return klass, options
      end

      # When asking for indexes on a table, the deferrable option will show
      # as nil if it wasn't set, we remove this to normalize it to the volt
      # options.
      def self.normalized_indexes_from_table(db, table_name)
        db.indexes(table_name).map do |name, options|
          # Remove the deferrable that defaults to false (nil)
          options = options.reject {|key, value| key == :deferrable && !value }
          [name, options]
        end.to_h
      end
    end
  end
end