module Lipsiadmin
  module Controller
    module Ext
      # Return column config, and store config/data for ExtJS ColumnModel and Store
      #
      #   Examples:
      #
      #     # app/controllers/backend/debtors_controller.rb
      #     def index
      #       @column_store = column_store_for Debtor do |cm|
      #         cm.add :id
      #         cm.add "full_name_or_company.upcase",   "Full Name",      :sortable => true, :dataIndex => :company
      #         cm.add :surname  # Header will be autogenerated
      #         cm.add :email,                          "Email",          :sortable => true
      #         cm.add :piva,                           "Piva",           :sortable => true
      #         cm.add :created_at,                     "Creato il",      :sortable => true, :renderer => :date, :align => :right
      #         cm.add :updated_at,                     "Aggiornato il",  :sortable => true, :renderer => :datetime, :align => :right
      #       end
      #
      #       respond_to do |format|
      #         format.js
      #         format.json do
      #           render :json => @column_store.store_data(params)
      #
      #           # or you can manually do:
      #             # debtors           = Debtor.search(params)
      #             # debtors_count     = debtors.size
      #             # debtors_paginated = debtors.paginate(params)
      #             # render :json => { :results => @column_store.store_data_from(debtors_paginated), :count => debtors_count }
      #         end
      #       end
      #     end
      #
      #     # app/views/backend/index.rjs
      #     page.grid do |grid|
      #       grid.id "debtors-grid" # If you don't set this columns are not saved in cookies
      #       grid.title "List al debtors"
      #       grid.base_path "/backend/debtors"
      #       grid.forgery_protection_token request_forgery_protection_token
      #       grid.authenticity_token form_authenticity_token
      #       grid.tbar  :default
      #       grid.store do |store|
      #         store.url "/backend/debtors.json"
      #         store.fields @column_store.store_fields
      #       end
      #       grid.columns do |columns|
      #         columns.fields @column_store.column_fields
      #       end
      #       grid.bbar  :store => grid.get_store, :pageSize => params[:limit] # Remember to add after defining store!
      #     end
      #
      def column_store_for(model, &block)
        ColumnStore.new(model, &block)
      end

      class ColumnStore#:nodoc:
        attr_reader :data

        def initialize(model, &block)#:nodoc
          @model = model
          @data = []
          yield self
        end

        # Method for add columns to the Column Model
        def add(*args)
          options = { :method => args[0] }
          options[:header]     = args[1].is_a?(String) || args[1].is_a?(Symbol) ? args[1].to_s : nil

          args.each { |a| options.merge!(a) if a.is_a?(Hash)  }

          # Add some defaults
          options[:header]   ||= options[:method].to_s
          options[:sortable]   = options[:sortable].nil? ? true : options[:sortable]

          # Try to translate header
          options[:header] = @model.human_attribute_name(options[:header].to_s)

          # Reformat DataIndex
          if options[:dataIndex].is_a?(Array)
            options[:dataIndex] = options[:dataIndex].collect do |f|
              f.is_a?(Symbol) ? "#{@model.table_name}.#{f}" : f
            end.join(",")
          end

          # Adding a name for our column
          options[:name] ||= "#{@model.table_name.singularize}[#{options[:method]}]"

          # Reformat query
          if options[:method].is_a?(Symbol)
            options[:dataIndex] ||= "#{@model.table_name}.#{options[:method]}"
          else
            # if we have eg. prodcedure.category.name we need to build
            # a sql finder action so we need to generate
            # procedures.categories.name
            columns = options[:method].split(".")
            if columns.empty?
              options[:dataIndex] ||= "#{@model.table_name}.#{options[:method]}"
            else
              options[:dataIndex] ||= columns[0..columns.length-2].collect(&:pluralize).join(".") + "." + columns.at(columns.size-1)
            end
          end

          # Reformat dataIndex
          options[:mapping] ||= options[:dataIndex].to_s.downcase.gsub(/[^a-z0-9]+/, '_').
                                                                  gsub(/-+$/, '_').
                                                                  gsub(/^-+$/, '_')
          # Now is necessary for our columns an ID
          options[:id] = options[:mapping]

          @data << options
        end

        # Return an array config for build an Ext.grid.ColumnModel() config
        def column_fields
          @data.clone.inject([]) do |fields, data|
            # Prevent to removing in the original Hash
            field = data.clone
            field.delete(:method)
            field.delete(:mapping)
            fields << field
            fields
          end
        end

        # Return an array config for build an Ext.data.GroupingStore()
        def store_fields
          @data.inject([]) do |fields, data|
            hash = { :name => data[:dataIndex], :mapping => data[:mapping] }
            hash.merge!(:type => data[:renderer]) if  data[:renderer] &&
                                                     (data[:renderer] == :date || data[:renderer] == :datetime || data[:renderer] == :time_to_date)
            fields << hash
            fields
          end
        end

        # Return data for a custom collection for the ExtJS Ext.data.GroupingStore() json
        def store_data_from(collection)
          collection.inject([]) do |store, c|
            store << @data.inject({ :id => c.id }) do |options, data|
              options[data[:mapping]] = (c.instance_eval(data[:method].to_s) rescue I18n.t("lipsiadmin.labels.not_found"))
              options
            end
            store
          end
        end

        # Return a searched and paginated data collection for the ExtJS Ext.data.GroupingStore() json
        # You can pass options like:
        #
        #   Examples
        #
        #     store_data(params, :conditions => "found = 1")
        #     store_data(params, :include => :posts)
        #
        def store_data(params, options={})
          # Some can tell me that this method made two identical queries one for count one for paginate.
          # We don't use the select count because in some circumstances require much time than select *.
          @model.send(:with_scope, :find => options) do
            collection           = @model.ext_search(params)
            collection_count     = collection.length
            collection_paginated = collection.ext_paginate(params)
            { :results => store_data_from(collection_paginated), :count => collection_count }
          end
        end

      end
    end
  end
end
