require "netzke/form_panel/form_panel_js"
require "netzke/form_panel/form_panel_api"
require "netzke/plugins/configuration_tool"
require "netzke/data_accessor"

module Netzke
  # = FormPanel
  # 
  # Represents Ext.form.FormPanel
  # 
  # == Configuration
  # * <tt>:model</tt> - name of the ActiveRecord model that provides data to this GridPanel.
  # * <tt>:record</tt> - record to be displayd in the form. Takes precedence over <tt>:record_id</tt>
  # * <tt>:record_id</tt> - id of the record to be displayd in the form. Also see <tt>:record</tt>
  # 
  # In the <tt>:ext_config</tt> hash (see Netzke::Base) the following FormPanel specific options are available:
  # 
  # * <tt>:mode</tt> - when set to <tt>:config</tt>, FormPanel loads in configuration mode
  class FormPanel < Base
    include FormPanelJs  # javascript (client-side)
    include FormPanelApi # API (server-side)
    include Netzke::DataAccessor # some code shared between GridPanel, FormPanel, and other widgets that use database attributes

    # Class-level configuration with defaults
    def self.config
      set_default_config({
        :config_tool_available       => true,
        
        :default_config => {
          :persistent_config => true,
          :ext_config => {
            :tools => []
          },
        }
      })
    end
    
    def initial_config
      res = super
      res[:ext_config][:bbar] = default_bbar if res[:ext_config][:bbar].nil?
      res
    end

    def default_bbar
      %w{ apply }
    end
    
    # Extra javascripts
    def self.include_js
      [
        "#{File.dirname(__FILE__)}/form_panel/javascripts/xcheckbox.js",
        Netzke::Base.config[:ext_location] + "/examples/ux/fileuploadfield/FileUploadField.js",
        "#{File.dirname(__FILE__)}/form_panel/javascripts/netzkefileupload.js"
      ]
    end
    
    api :netzke_submit, :netzke_load, :get_combobox_options

    attr_accessor :record
    
    def initialize(*args)
      super
      apply_helpers
      @record = config[:record] || config[:record_id] && data_class && data_class.find(:first, :conditions  => {data_class.primary_key => config[:record_id]})
    end
    
    # (We can't memoize this method because at some point we extend it, e.g. in Netzke::DataAccessor)
    def data_class
      ::ActiveSupport::Deprecation.warn("data_class_name option is deprecated. Use model instead", caller) if config[:data_class_name]
      model_name = config[:model] || config[:data_class_name]
      @data_class ||= model_name && model_name.constantize
    end
    
    def configuration_widgets
      res = []
      
      res << {
        :name              => 'fields',
        :widget_class_name => "FieldsConfigurator",
        :active            => true,
        :widget            => self,
        :persistent_config => true
      }

      res << {
        :name               => 'general',
        :widget_class_name  => "PropertyEditor",
        :widget             => self,
        :ext_config         => {:title => false}
      }
      
      res
    end

    def actions
      {
        :apply => {:text => 'Apply'}
      }
    end
    
    def columns
      @columns ||= get_columns.deep_convert_keys{|k| k.to_sym}
    end
    
    # parameters used to instantiate the JS object
    def js_config
      res = super
      res.merge!(:clmns => columns)
      res.merge!(:data_class_name => data_class.name) if data_class
      res.merge!(:pri => data_class.primary_key) if data_class
      res
    end
 
    # columns to be displayed by the FieldConfigurator (which is GridPanel-based)
    def self.config_columns
      [
        {:name => :name, :type => :string, :editor => :combobox, :width => 200},
        {:name => :hidden, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Excl"},
        {:name => :disabled, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Dis"},
        {:name => :xtype, :type => :string, :editor => {:xtype => :combobox, :options => Netzke::Ext::FORM_FIELD_XTYPES}},
        {:name => :value, :type => :string},
        {:name => :field_label, :type => :string},
        {:name => :input_type, :type => :string, :hidden => true}
      ]
    end
 
    def self.property_fields
      res = [
        {:name => :ext_config__title,               :type => :string},
        {:name => :ext_config__header,              :type => :boolean, :default => true},
        {:name => :ext_config__bbar,              :type => :json}
      ]
      
      res
    end
 
    # Normalized columns
    def normalized_columns
      @normalized_columns ||= normalize_columns(columns)
    end
    
 
    def get_columns
      if persistent_config_enabled?
        persistent_config['layout__columns'] ||= default_columns
        res = normalize_array_of_columns(persistent_config['layout__columns'])
      else
        res = default_columns
      end

      # merge values for each field if the record is specified
      @record && res.map! do |c|
        value = @record.send(normalize_column(c)[:name])
        value.nil? ? c : normalize_column(c).merge(:value => value)
      end

      res
    end
    
    XTYPE_MAP = {
      :integer => :numberfield,
      :boolean => :xcheckbox,
      :date => :datefield,
      :datetime => :xdatetime,
      :text => :textarea,
      :json => :jsonfield
      # :string => :textfield
    }
    
    def default_columns
      # columns specified in widget's config
      columns_from_config = config[:columns] && normalize_columns(config[:columns])
      
      if columns_from_config
        # reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
        for c in columns_from_config
          corresponding_exposed_column = predefined_columns.find{ |k| k[:name] == c[:name] }
          c.reverse_merge!(corresponding_exposed_column) if corresponding_exposed_column
        end
        columns_for_create = columns_from_config
      elsif predefined_columns
        # we didn't have columns configured in widget's config, so, use the columns from the data class
        columns_for_create = predefined_columns
      else
        raise ArgumentError, "No columns specified for widget '#{global_id}'"
      end
      
      columns_for_create.map! do |c|
        if data_class
          # Try to figure out the configuration from data class
          # detect :assoc__method
          if c[:name].to_s.index('__')
            assoc_name, method = c[:name].to_s.split('__').map(&:to_sym)
            if assoc = data_class.reflect_on_association(assoc_name)
              assoc_column = assoc.klass.columns_hash[method.to_s]
              assoc_method_type = assoc_column.try(:type)
              if assoc_method_type
                c[:xtype] ||= XTYPE_MAP[assoc_method_type] == :xcheckbox ? :xcheckbox : :combobox
              end
            end
          end
      
          # detect association column (e.g. :category_id)
          if assoc = data_class.reflect_on_all_associations.detect{|a| a.primary_key_name.to_sym == c[:name]}
            c[:xtype] ||= :combobox
            assoc_method = %w{name title label id}.detect{|m| (assoc.klass.instance_methods + assoc.klass.column_names).include?(m) } || assoc.klass.primary_key
            c[:name] = "#{assoc.name}__#{assoc_method}".to_sym
          end
          c[:hidden] = true if c[:name] == data_class.primary_key.to_sym && c[:hidden].nil? # hide ID column by default

        end
        
        # detect column type
        type = c[:type] || data_class && data_class.columns_hash[c[:name].to_s].try(:type) || :string
        c[:type] ||= type
        
        c[:xtype] ||= XTYPE_MAP[type] unless XTYPE_MAP[type].nil?

        # if the column is finally simply {:name => "something"}, cut it down to "something"
        c.reject{ |k,v| k == :name }.empty? ? c[:name] : c
      end
      
      columns_for_create
      
    end
     
    include Plugins::ConfigurationTool if config[:config_tool_available] # it will load ConfigurationPanel into a modal window      
  end
end