
module DeltaForce
  class ChangeProxy

    attr_reader :klass

    def initialize(klass)
      @klass = klass
    end

    def values(*value_fields)
      options = value_fields.extract_options!.symbolize_keys

      partition_key = options[:by].to_s
      partition_column = "#{table_name}.#{partition_key}"
      id_column = "#{table_name}.id"

      period_field_name = options[:over].to_s
      period_column = "#{table_name}.#{period_field_name}"

      scope_name = options[:scope_name] || default_scope_name(value_fields, options)

      window = "
        (
           PARTITION BY #{partition_column}
           ORDER BY #{period_column} DESC, #{id_column} DESC ROWS
           BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
        )"

      value_expressions = value_fields.collect do |value_field|
        value_field_name = value_field.to_s
        value_column = "#{table_name}.#{value_field_name}"

        [
          "last_value(#{value_column}) over #{window} as opening_#{value_field_name}",
          "first_value(#{value_column}) over #{window} as closing_#{value_field_name}"
        ]
      end.flatten

      klass.named_scope scope_name, :select => "distinct #{partition_column},
        last_value(#{period_column}) over #{window} as opening_#{period_field_name},
        first_value(#{period_column}) over #{window} as closing_#{period_field_name},
        #{value_expressions.join ','}
      "

      value_fields.each do |value_field|
        klass.class.send :define_method, "#{value_field}_by_#{options[:by].to_s}_as_of_#{options[:over].to_s}" do |period|
          Hash[
            scoped(
              :select => "distinct #{partition_column},
                first_value(#{table_name}.#{value_field.to_s}) over #{window} as #{value_field.to_s}",
              :conditions => [ "#{period_column} <= ?", period]
            ).index_by(&partition_key.to_sym).map{|k,v| [k, v[value_field]]}
          ]
        end
      end
    end

    alias :value :values

    private

    def default_scope_name(value_fields, options)
      value_fields = value_fields.map(&:to_s).sort
      period = options[:over].to_s
      partition = options[:by].to_s

      value_fields = value_fields.to_sentence(:words_connector => '_and_',
                                              :two_words_connector => '_and_',
                                              :last_word_connector => '_and_')

      "changes_in_#{value_fields}_by_#{partition}_over_#{period}"
    end

    def table_name
      klass.table_name
    end

    def calculate_changes(value_field_name, options = {})
      options = options.symbolize_keys

      value_field_name = value_field_name.to_s

      partition_field_name = options[:partition_by]
      partition_column = "#{table_name}.#{partition_field_name.to_s}"

      value_column = "#{table_name}.#{value_field_name}"
      id_column = "#{table_name}.id"

      period_field_name = options[:period] || 'period'
      period_column = "#{table_name}.#{period_field_name}"

      scope_name = "changes_in_#{value_field_name}".to_sym

      window = "
        (
           PARTITION BY #{partition_column}
           ORDER BY #{period_column} DESC, #{id_column} DESC ROWS
           BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
        )"

      named_scope scope_name, :select => "distinct #{partition_column},
        last_value(#{period_column}) over #{window} as opening_#{period_field_name},
        first_value(#{period_column}) over #{window} as closing_#{period_field_name},
        last_value(#{value_column}) over #{window} as opening_#{value_field_name},
        first_value(#{value_column}) over #{window} as closing_#{value_field_name}
        "
    end
  end
end
