module Lipsiadmin
  module Ext
    # Generate a full customizable Ext.GridPanel
    #
    # Examples:
    #
    #   # Returns:
    #   #   var grid = new Ext.grid.GridPanel({
    #   #     clicksToEdit: 1,
    #   #     border: false,
    #   #     bodyBorder: false,
    #   #     store: store,
    #   #     view: groupingView,
    #   #     region: "center",
    #   #     sm: new Ext.grid.CheckboxSelectionModel(),
    #   #     bbar: pagingToolbar,
    #   #     plugins: [new Ext.grid.Search()],
    #   #     viewConfig: { forceFit: true },
    #   #     id: "grid-posts",
    #   #     cm: columnModel,
    #   #     tbar: toolBar
    #   #   });
    #
    #   page.grid do |grid|
    #     grid.id "grid-posts"
    #     grid.title "List all Post"
    #     grid.base_path "/backend/posts"
    #     grid.forgery_protection_token request_forgery_protection_token
    #     grid.authenticity_token form_authenticity_token
    #     grid.tbar  :default # or [:add, :edit, :delete] or [:edit, :delete]
    #     grid.store do |store|
    #       store.url "/backend/posts.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]
    #   end
    #
    #   # Returns:
    #   #   var grid = new Ext.grid.EditorGridPanel({
    #   #     clicksToEdit: 1,
    #   #   ...
    #
    #   page.grid :editable => true do |grid|
    #     grid.id "grid-posts"
    #     ...
    #
    class Grid < Component

      def initialize(options={}, &block)#:nodoc:
        # Call Super Class for initialize configuration
        @editable = options.delete(:editable)
        super("Ext.grid.#{@editable ? 'EditorGridPanel' : 'GridPanel' }", { :plugins => [] }.merge(options))

        # Write default configuration if not specified
        config[:plugins]      ||= []
        viewConfig            :forceFit => true
        border                false
        bodyBorder            false
        template              :default
        clicksToEdit          1
        region                "center"
        sm                    :checkbox
        view                  :default
        render                true
        config[:plugins] <<   "new Ext.grid.Search()".to_l

        # We need to add a setTimeout because, we destroy
        # the grid before loading a new page/js.
        on(:dblclick) do |p|
          p.delay(0.2) { p.call :edit }
        end

        yield self if block_given?
      end

      # Define the selection model of this grid.
      # You can pass:
      #
      # * :checkbox || :default
      # * :row
      # * custom (eg. Component.new("some"))
      #
      # It generate some like:
      #
      #   new Ext.grid.CheckboxSelectionModel()
      #
      def sm(object)
        selmodel = case object
          when :default  then Component.new("Ext.grid.CheckboxSelectionModel", :prefix => get_var)
          when :checkbox then Component.new("Ext.grid.CheckboxSelectionModel", :prefix => get_var)
          when :row      then Component.new("Ext.grid.RowSelectionModel",      :prefix => get_var)
          else object
        end
        add_object(:sm, selmodel)
      end

      # Generate or set a new Ext.Toolbar
      # You can pass tbar :default options that will create
      # defaults buttons for add, edit and remove records, it's generate also
      # the javascript for manage them.
      # for use this you need to set for the grid the: +base_path+, +forgery_protection_token+,
      # +authenticity_token+ and +store+.
      #
      #   Examples:
      #     var toolBar = new Ext.Toolbar([{
      #         handler: add,
      #         disabled: false,
      #         text: Backend.locale.buttons.add,
      #         cls: "x-btn-text-icon add",
      #         id: "add"
      #      },{
      #         handler: edit,
      #         disabled: true,
      #         text: Backend.locale.buttons.edit,
      #         cls: "x-btn-text-icon edit",
      #         id: "edit"
      #      },{
      #         handler: remove,
      #         disabled: true,
      #         text: Backend.locale.buttons.remove,
      #         cls: "x-btn-text-icon remove",
      #         id: "remove"
      #     }]);
      #
      #   tbar  :default
      #
      def tbar(object=nil, &block)
        tbar = object.is_a?(ToolBar) ? object : ToolBar.new(:prefix => get_var)
        if object == :default || object == :all
          tbar.add_button :text => "Backend.locale.buttons.add".to_l,    :id => "add",    :disabled => false, :cls => "x-btn-text-icon add",    :handler => "add".to_l
          tbar.add_button :text => "Backend.locale.buttons.edit".to_l,   :id => "edit",   :disabled => true,  :cls => "x-btn-text-icon edit",   :handler => "edit".to_l
          tbar.add_button :text => "Backend.locale.buttons.remove".to_l, :id => "remove", :disabled => true,  :cls => "x-btn-text-icon remove", :handler => "remove".to_l
          @ttbar_add, @ttbar_edit, @ttbar_delete = true, true, true
        end
        if object.is_a?(Array)
          object.each do |button|
            case button
              when :add
                tbar.add_button :text => "Backend.locale.buttons.add".to_l,    :id => "add",    :disabled => false, :cls => "x-btn-text-icon add",    :handler => "add".to_l
                @ttbar_add = true
              when :edit
                tbar.add_button :text => "Backend.locale.buttons.edit".to_l,   :id => "edit",   :disabled => true,  :cls => "x-btn-text-icon edit",   :handler => "edit".to_l
                @ttbar_edit = true
              when :delete
                tbar.add_button :text => "Backend.locale.buttons.remove".to_l, :id => "remove", :disabled => true,  :cls => "x-btn-text-icon remove", :handler => "remove".to_l
                @ttbar_delete = true
            end
          end
        end
        yield tbar if block_given?
        add_object(:tbar, tbar)
      end

      # Generate or set a new Ext.Toolbar
      #
      #   Examples:
      #     bbar: new Ext.PagingToolbar({
      #       pageSize: <%= params[:limit] %>,
      #       store: js,
      #       displayInfo: true
      #     })
      #   bbar :pageSize => params[:limit], :store => store.get_var, displayInfo: true
      #
      def bbar(object=nil, &block)
        bbar = object.is_a?(Hash) ? Component.new("Ext.PagingToolbar", object.merge(:prefix => get_var)) : object
        add_object(:bbar, bbar)
      end

      # Generate or set a new Ext.grid.GroupingView
      # You can pass view :default options that will autocreate a correct GroupingView
      #
      #   Examples:
      #     view: new Ext.grid.GroupingView({
      #       forceFit:true,
      #       groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Foo" : "Bar"]})'
      #     })
      #   view :forceFit => true, :groupTextTpl => '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Foo" : "Bar"]})'
      #
      def view(object=nil, &block)
        view = case object
          when :default then Component.new("Ext.grid.GroupingView", { :forceFit => true, :prefix => get_var })
          when Hash     then Component.new("Ext.grid.GroupingView", { :forceFit => true, :prefix => get_var }.merge(object))
          else object
        end
        add_object(:view, view)
      end

      # Generate or set a new Ext.data.GroupingStore
      def store(object=nil, &block)
        store = object.is_a?(Store) ? object : Store.new(:prefix => get_var, &block)
        add_object(:store, store)
      end

      # Generate or set new Ext.grid.ColumnModel
      def columns(object=nil, &block)
        options = { :columns => [] }
        if config[:sm] && before.find { |js| js.start_with?("var #{config[:sm]} = new Ext.grid.CheckboxSelectionModel") }
          options[:columns] << config[:sm]
        end
        cm = object.is_a?(ColumnModel) ? value : ColumnModel.new(options.merge(:prefix => get_var), &block)
        add_object(:cm, cm)
      end

      # Define the template to use for build grid functions (add/delete/edit)
      #
      # Default we use:
      #
      #   /path/to/lipsiadmin/lib/view/helpers/ext/templates/grid_functions.js.erb
      #
      # But you can easy add your own paths like:
      #
      #   Lipsiadmin::Ext::Component.template_paths.unshift("/path/to/my/js/templates")
      #
      # Or direct by grid:
      #
      #   # products/show.rjs
      #   page.grid :editable => true do |grid|
      #     grid.id "grid-products"
      #     grid.template "products/grid_functions"
      #   ...
      #
      def template(value)
        @template = value == :default ? :grid_functions : value
      end

      # Define if the grid need to be added to contentDynamic
      #
      #   Backend.app.addItem(#{get_var});
      #
      def render(value)
        @render = value
      end

      # The base_path used for ToolBar, it's used for generate [:new, :edit, :destory] urls
      def base_path(value)
        @base_path = value
      end

      # The path for ToolBar New Button, if none given we use the base_path
      def new_path(value)
        @new_path = value
      end

      # The path for ToolBar Edit Button, if none given we use the base_path
      def edit_path(value)
        @edit_path = value
      end

      # The path for ToolBar Delete Button, if none given we use the base_path
      def destroy_path(value)
        @destroy_path = value
      end

      # The forgery_protection_token used for ToolBar
      def forgery_protection_token(value)
        @forgery_protection_token = value
      end

      # The authenticity_token used for ToolBar
      def authenticity_token(value)
        @authenticity_token = value
      end

      # Returns getSelectionModel().getSelected()
      #
      #   Examples:
      #
      #     # Generates: grid.getSelectionModel().getSelected().id
      #     grid.get_selected
      #
      #     # Generates: getSelectionModel().getSelected().data['name']
      #     grid.get_selected(:name)
      #
      def get_selected(data=:id)
        raise_error "No Column Selection Model Defined" if config[:sm].blank?
        if data.to_sym == :id
         "#{config[:sm]}.getSelected().id".to_l
        else
          "#{config[:sm]}.getSelected().data[#{data.to_json}]"
        end
      end

      # Return the javascript for create a new Ext.grid.GridPanel
      def to_s
        if @ttbar_add || @ttbar_edit || @ttbar_delete
          raise_error "You must provide the base_path for autobuild toolbar."                      if @base_path.blank? && @new_path.blank? && @edit_path.blank? && @destroy_path.blank?
          raise_error "You must provide the new_path for autobuild button new of toolbar."         if @base_path.blank? && @new_path.blank?
          raise_error "You must provide the edit_path for autobuild button edit of toolbar."       if @base_path.blank? && @edit_path.blank?
          raise_error "You must provide the destroy_path for autobuild button destroy of toolbar." if @base_path.blank? && @destroy_path.blank?
          raise_error "You must provide the forgery_protection_token for autobuild toolbar."       if @forgery_protection_token.blank?
          raise_error "You must provide the authenticity_token for autobuild toolbar."             if @authenticity_token.blank?
          raise_error "You must provide the grid for autobuild toolbar."                           if get_var.blank?
          raise_error "You must provide the selection model for autobuild toolbar."                if config[:sm].blank?
          raise_error "You must provide the store."                                                if config[:store].blank?
        end

        after << render_javascript(@template, :var => get_var, :store => config[:store], :sm => config[:sm], :tbar => config[:tbar], :editable => @editable, :un => @un)

        if @render
          after << "Backend.app.addItem(#{get_var});" if @render
          if config[:store]
            after << "#{config[:store]}.on('beforeload', function(){ Backend.app.mask(); });"
            after << "#{config[:store]}.on('load', function(){ Backend.app.unmask(); });"
            after << "#{config[:store]}.load();"
          end
        end
        super
      end
    end
  end
end
