module Netzke
  module Grid
    # Implements Grid server-side operations. Used by the Endpoints module.
    module Services
      # Implementation for the "server_read" endpoint
      def read(params = {})
        {}.tap do |res|
          records = get_records(params)
          res[:data] = records.map{|r| model_adapter.record_to_array(r, final_columns)}
          res[:total] = count_records(params)
        end
      end

      # Returns hash with results per client (temporary) id, e.g.:
      #
      #   {
      #     1: {errors: [], record: {title: 'New title'}},
      #     2: {errors: ["Title must be present"], record: nil}
      #   }
      def create(data)
        data.inject({}) do |out, attrs|
          id = attrs.delete('internal_id')
          record = model_adapter.new_record
          out.merge(id => update_record(record, attrs))
        end
      end

      # Receives an array of attribute hashes, e.g.:
      #
      #   [{"id": 1, title: "New title"}, {"id": 2, title: ""}]
      #
      # Returns hash with results per id, e.g.:
      #
      #   {
      #     1: {errors: [], record: {title: 'New title'}},
      #     2: {errors: ["Title must be present"], record: nil}
      #   }
      def update(data)
        data.inject({}) do |out, attrs|
          id = attrs.delete(model_adapter.primary_key)
          record = model_adapter.find_record(id, scope: config.scope)
          record.nil? ? out.merge(id => {error: "Not allowed to edit record #{id}"}) : out.merge(id => update_record(record, attrs))
        end
      end

      # Destroys records by ids
      # Returns hash with results per id, e.g.:
      #
      #   {
      #     1: "ok",
      #     2: "error: "This record could not be destroyed"
      #   }
      def destroy(ids)
        out = {}

        ids.each {|id|
          record = model_adapter.find_record(id, scope: config[:scope])
          next if record.nil?

          if record.destroy
            out[id] = "ok"
          else
            out[id] = {error: record.errors.to_a.first}
          end
        }

        out
      end

      # Returns an array of records.
      def get_records(params)
        params[:filters] = normalize_filters(params[:filters]) if params[:filters]
        params[:query] = normalize_query(params[:query]) if params[:query].present?
        params[:scope] = config[:scope] # note, params[:scope] becomes ActiveSupport::HashWithIndifferentAccess

        model_adapter.get_records(params, final_columns)
      end

      def count_records(params)
        params[:scope] = config[:scope] # note, params[:scope] becomes ActiveSupport::HashWithIndifferentAccess

        model_adapter.count_records(params, final_columns)
      end

      # Override this method to react on each operation that caused changing of data
      def on_data_changed
      end

    protected

      def normalize_filters(filters)
        filters.map do |f|
          { attr: f["property"], value: f["value"], operator: f["operator"] }.tap do |norm|

            # Ext JS filters send us date in the American format
            if f["value"].is_a?(String) && f["value"].match(/^(\d\d)\/(\d\d)\/(\d\d\d\d)$/)
              norm[:value] = "#{$3}-#{$1}-#{$2}"
            end

            if filter_with = final_columns_hash[norm[:attr].to_sym][:filter_with]
              norm[:proc] = filter_with
            end

            norm[:operator] = "contains" if f["type"] == "string"
          end
        end
      end

      def normalize_query(or_query)
        or_query.each do |and_query|
          and_query.each do |q|
            column_config = final_columns_hash[q[:attr].to_sym] || {}
            if filter_with = column_config[:filter_with]
              q[:proc] = filter_with
            end
          end
        end
      end

      def update_record(record, attrs)
        attrs.each_pair do |k,v|
          attr = final_columns_hash[k.to_sym]
          next if attr.nil?
          model_adapter.set_record_value_for_attribute(record, attr, v)
        end

        strong_attrs = config[:strong_values] || {}

        strong_attrs.each_pair do |k,v|
          model_adapter.set_record_value_for_attribute(record, {name: k.to_s}, v)
        end

        if record.save
          {record: model_adapter.record_to_array(record, final_columns)}
        else
          {error: record.errors.to_a}
        end
      end
    end
  end
end
