module Netzke
  module Basepack
    class Grid < Netzke::Base
      module Services
        extend ActiveSupport::Concern

        included do
          endpoint :get_data do |params, this|
            # not a usual Netzke endpoint, as it's being used by the Ext.data.DirectStore
            this.merge! get_data(params)
          end

          endpoint :post_data do |params, this|
            mod_records = {}
            [:create, :update].each do |operation|
              data = ActiveSupport::JSON.decode(params["#{operation}d_records"]) if params["#{operation}d_records"]
              if !data.nil? && !data.empty? # data may be nil for one of the operations
                mod_records[operation] = process_data(data, operation)
                mod_records[operation] = nil if mod_records[operation].empty?
              end
            end

            on_data_changed

            this.update_new_records mod_records[:create]
            this.update_mod_records mod_records[:update] if mod_records[:update]
            this.netzke_feedback @flash
          end

          endpoint :delete_data do |params, this|
            if !config[:prohibit_delete]
              record_ids = ActiveSupport::JSON.decode(params[:records])
              data_adapter.destroy(record_ids)
              on_data_changed
              this.netzke_feedback I18n.t('netzke.basepack.grid.deleted_n_records', :n => record_ids.size)
              this.load_store_data get_data
            else
              this.netzke_feedback I18n.t('netzke.basepack.grid.cannot_delete')
            end
          end

          endpoint :resize_column do |params, this|
            raise "Called resize_column endpoint while not configured to do so" if !config[:persistence]

            current_columns_order = state[:columns_order] || initial_columns_order
            current_columns_order[normalize_index(params[:index].to_i)][:width] = params[:size].to_i
            state[:columns_order] = current_columns_order
          end

          endpoint :move_column do |params, this|
            raise "Called move_column endpoint while not configured to do so" if !config[:persistence]

            remove_from = normalize_index(params[:old_index].to_i)
            insert_to = normalize_index(params[:new_index].to_i)

            current_columns_order = state[:columns_order] || initial_columns_order

            column_to_move = current_columns_order.delete_at(remove_from)
            current_columns_order.insert(insert_to, column_to_move)

            state[:columns_order] = current_columns_order
          end

          endpoint :hide_column do |params, this|
            raise "Called hide_column endpoint while not configured to do so" if !config[:persistence]
            current_columns_order = state[:columns_order] || initial_columns_order
            current_columns_order[normalize_index(params[:index].to_i)][:hidden] = params[:hidden]
            state[:columns_order] = current_columns_order
          end

          # Returns options for a combobox
          # params receive:
          # +attr+ - column's name
          # +query+ - what's typed-in in the combobox
          # +id+ - selected record id
          endpoint :get_combobox_options do |params, this|
            column = final_columns.detect{ |c| c[:name] == params[:attr] }
            this.data = data_adapter.combo_data(column, params[:query])
          end

          endpoint :move_rows do |params, this|
            data_adapter.move_records(params)
          end

          # When providing the edit_form component, fill in the form with the requested record
          endpoint :deliver_component do |params, this|
            if params[:name] == 'edit_window'
              components[:edit_window].form_config.record_id = params[:record_id].to_i
            end

            super(params, this)
          end

          # TODO: functionality of the following 2 endpoints could probably be improved by subclassing Basepack::Form as a dedicated form for adding/editing records in a grid.
          # Process the submit of multi-editing form ourselves
          endpoint :multi_edit_window__multi_edit_form__netzke_submit do |params, this|
            ids = ActiveSupport::JSON.decode(params.delete(:ids))
            data = ids.collect{ |id| ActiveSupport::JSON.decode(params[:data]).merge("id" => id) }

            data.map!{|el| el.delete_if{ |k,v| v.is_a?(String) && v.blank? }} # only interested in set values

            mod_records_count = process_data(data, :update).count

            # remove duplicated flash messages
            @flash = @flash.inject([]){ |r,hsh| r.include?(hsh) ? r : r.push(hsh) }

            if mod_records_count > 0
              on_data_changed
              this.netzke_set_result("ok")
              this.on_submit_success
            end

            this.netzke_feedback(@flash)
          end

          # The following two look a bit hackish, but serve to invoke on_data_changed when a form gets successfully submitted
          endpoint :add_window__add_form__netzke_submit do |params, this|
            this.merge!(component_instance(:add_window__add_form).invoke_endpoint(:netzke_submit, params))
            on_data_changed if this.set_form_values.present?
            this.delete(:set_form_values)
          end

          endpoint :edit_window__edit_form__netzke_submit do |params, this|
            this.merge!(component_instance(:edit_window__edit_form).invoke_endpoint(:netzke_submit, params))
            on_data_changed if this.set_form_values.present?
            this.delete(:set_form_values)
          end
        end # included

        # Implementation for the "get_data" endpoint
        def get_data(*args)
          params = args.first || {} # params are optional!
          if !config[:prohibit_read]
            {}.tap do |res|
              records = get_records(params)
              res[:data] = records.map{|r| data_adapter.record_to_array(r, final_columns(:with_meta => true))}
              res[:total] = count_records(params)  if config[:enable_pagination]
            end
          else
            flash :error => "You don't have permissions to read data"
            { :netzke_feedback => @flash }
          end
        end

      protected

        # Returns an array of records.
        def get_records(params)
          params[:limit] = config[:rows_per_page] if config[:enable_pagination]
          params[:scope] = config[:scope] # note, params[:scope] becomes ActiveSupport::HashWithIndifferentAccess

          data_adapter.get_records(params, final_columns)
        end

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

          data_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

        # Given an index of a column among enabled (non-excluded) columns, provides the index (position) in the table
        def normalize_index(index)
          norm_index = 0
          index.times do
            while true do
              norm_index += 1
              break unless final_columns[norm_index][:included] == false
            end
          end
          norm_index
        end

        # Params:
        # <tt>:operation</tt>: :update or :create
        def process_data(data, operation)
          success = true
          mod_records = {}
          if !config[:"prohibit_#{operation}"]
            modified_records = 0
            data.each do |record_hash|
              id = record_hash.delete('id')
              record = operation == :create ? data_adapter.new_record : data_adapter.find_record(id)
              success = true

              # merge with strong default attirbutes
              record_hash.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]

              record_hash.each_pair do |k,v|
                data_adapter.set_record_value_for_attribute(record, final_columns_hash[k.to_sym].nil? ? {:name => k} : final_columns_hash[k.to_sym], v, config.role || :default)
              end

              # try to save
              mod_records[id] = data_adapter.record_to_array(record, final_columns(:with_meta => true)) if success && record.save

              # flash eventual errors
              if !record.errors.empty?
                success = false
                record.errors.to_a.each do |msg|
                  flash :error => msg
                end
              end
            end
          else
            success = false
            flash :error => "You don't have permissions to #{operation} data"
          end
          mod_records
        end
      end
    end
  end
end