module ActiveRecord

  module ClassMethods

    def base_class

      unless self < Base
        raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
      end

      if superclass == Base || superclass.abstract_class?
        self
      else
        superclass.base_class
      end

    end

    def abstract_class?
      defined?(@abstract_class) && @abstract_class == true
    end

    def primary_key
      base_class.instance_eval { @primary_key_value || :id }
    end

    def primary_key=(val)
     base_class.instance_eval {  @primary_key_value = val }
    end

    def inheritance_column
      base_class.instance_eval {@inheritance_column_value || "type"}
    end

    def inheritance_column=(name)
      base_class.instance_eval {@inheritance_column_value = name}
    end

    def model_name
      # in reality should return ActiveModel::Name object, blah blah
      name
    end

    def find(id)
      base_class.instance_eval {ReactiveRecord::Base.find(self, primary_key, id)}
    end

    def find_by(opts = {})
      base_class.instance_eval {ReactiveRecord::Base.find(self, opts.first.first, opts.first.last)}
    end

    def enum(*args)
      # when we implement schema validation we should also implement value checking
    end

    def serialize(attr, *args)
      ReactiveRecord::Base.serialized?[self][attr] = true
    end

    # ignore any of these methods if they get called on the client.   This list should be trimmed down to include only
    # methods to be called as "macros" such as :after_create, etc...
    SERVER_METHODS = [
      :attribute_type_decorations, :defined_enums, :_validators, :timestamped_migrations, :lock_optimistically, :lock_optimistically=,
      :local_stored_attributes=, :lock_optimistically?, :attribute_aliases?, :attribute_method_matchers?, :defined_enums?,
      :has_many_without_reactive_record_add_changed_method, :has_many_with_reactive_record_add_changed_method,
      :belongs_to_without_reactive_record_add_changed_method, :belongs_to_with_reactive_record_add_changed_method,
      :cache_timestamp_format, :composed_of_with_reactive_record_add_changed_method, :schema_format, :schema_format=,
      :error_on_ignored_order_or_limit, :error_on_ignored_order_or_limit=, :timestamped_migrations=, :dump_schema_after_migration,
      :dump_schema_after_migration=, :dump_schemas, :dump_schemas=, :warn_on_records_fetched_greater_than=,
      :belongs_to_required_by_default, :default_connection_handler, :connection_handler=, :default_connection_handler=,
      :skip_time_zone_conversion_for_attributes, :skip_time_zone_conversion_for_attributes=, :time_zone_aware_types,
      :time_zone_aware_types=, :protected_environments, :skip_time_zone_conversion_for_attributes?, :time_zone_aware_types?,
      :partial_writes, :partial_writes=, :composed_of_without_reactive_record_add_changed_method, :logger, :partial_writes?,
      :after_initialize, :record_timestamps, :record_timestamps=, :after_find, :after_touch, :before_save, :around_save,
      :belongs_to_required_by_default=, :default_connection_handler?, :before_create, :around_create, :before_update, :around_update,
      :after_save, :before_destroy, :around_destroy, :after_create, :after_destroy, :after_update, :_validation_callbacks,
      :_validation_callbacks?, :_validation_callbacks=, :_initialize_callbacks, :_initialize_callbacks?, :_initialize_callbacks=,
      :_find_callbacks, :_find_callbacks?, :_find_callbacks=, :_touch_callbacks, :_touch_callbacks?, :_touch_callbacks=, :_save_callbacks,
      :_save_callbacks?, :_save_callbacks=, :_create_callbacks, :_create_callbacks?, :_create_callbacks=, :_update_callbacks,
      :_update_callbacks?, :_update_callbacks=, :_destroy_callbacks, :_destroy_callbacks?, :_destroy_callbacks=, :record_timestamps?,
      :_synchromesh_scope_args_check, :pre_synchromesh_scope, :pre_synchromesh_default_scope, :do_not_synchronize, :do_not_synchronize?,
      :logger=, :maintain_test_schema, :maintain_test_schema=, :scope, :time_zone_aware_attributes, :time_zone_aware_attributes=,
      :default_timezone, :default_timezone=, :_attr_readonly, :warn_on_records_fetched_greater_than, :configurations, :configurations=,
      :_attr_readonly?, :table_name_prefix=, :table_name_suffix=, :schema_migrations_table_name=, :internal_metadata_table_name,
      :internal_metadata_table_name=, :primary_key_prefix_type, :_attr_readonly=, :pluralize_table_names=, :protected_environments=,
      :ignored_columns=, :ignored_columns, :index_nested_attribute_errors, :index_nested_attribute_errors=, :primary_key_prefix_type=,
      :table_name_prefix?, :table_name_suffix?, :schema_migrations_table_name?, :internal_metadata_table_name?, :protected_environments?,
      :pluralize_table_names?, :ignored_columns?, :store_full_sti_class, :store_full_sti_class=, :nested_attributes_options,
      :nested_attributes_options=, :store_full_sti_class?, :default_scopes, :default_scope_override, :default_scopes=, :default_scope_override=,
      :nested_attributes_options?, :cache_timestamp_format=, :cache_timestamp_format?, :reactive_record_association_keys, :_validators=,
      :has_many, :belongs_to, :composed_of, :belongs_to_without_reactive_record_add_is_method, :_rollback_callbacks, :_commit_callbacks,
      :_before_commit_callbacks, :attribute_type_decorations=, :_commit_callbacks=, :_commit_callbacks?, :_before_commit_callbacks?,
      :_before_commit_callbacks=, :_rollback_callbacks=, :_before_commit_without_transaction_enrollment_callbacks?,
      :_before_commit_without_transaction_enrollment_callbacks=, :_commit_without_transaction_enrollment_callbacks,
      :_commit_without_transaction_enrollment_callbacks?, :_commit_without_transaction_enrollment_callbacks=, :_rollback_callbacks?,
      :_rollback_without_transaction_enrollment_callbacks?, :_rollback_without_transaction_enrollment_callbacks=,
      :_rollback_without_transaction_enrollment_callbacks, :_before_commit_without_transaction_enrollment_callbacks, :aggregate_reflections,
      :_reflections=, :aggregate_reflections=, :pluralize_table_names, :public_columns_hash, :attributes_to_define_after_schema_loads,
      :attributes_to_define_after_schema_loads=, :table_name_suffix, :schema_migrations_table_name, :attribute_aliases,
      :attribute_method_matchers, :connection_handler, :attribute_aliases=, :attribute_method_matchers=, :_validate_callbacks,
      :_validate_callbacks?, :_validate_callbacks=, :_validators?, :_reflections?, :aggregate_reflections?, :include_root_in_json,
      :_reflections, :include_root_in_json=, :include_root_in_json?, :local_stored_attributes, :default_scope, :table_name_prefix,
      :attributes_to_define_after_schema_loads?, :attribute_type_decorations?, :defined_enums=, :suppress, :has_secure_token,
      :generate_unique_secure_token, :store, :store_accessor, :_store_accessors_module, :stored_attributes, :reflect_on_aggregation,
      :reflect_on_all_aggregations, :_reflect_on_association, :reflect_on_all_associations, :clear_reflections_cache, :reflections,
      :reflect_on_association, :reflect_on_all_autosave_associations, :no_touching, :transaction, :after_commit, :after_rollback, :before_commit,
      :before_commit_without_transaction_enrollment, :after_create_commit, :after_update_commit, :after_destroy_commit,
      :after_commit_without_transaction_enrollment, :after_rollback_without_transaction_enrollment, :raise_in_transactional_callbacks,
      :raise_in_transactional_callbacks=, :accepts_nested_attributes_for, :has_secure_password, :has_one, :has_and_belongs_to_many,
      :before_validation, :after_validation, :serialize, :primary_key, :dangerous_attribute_method?, :get_primary_key, :quoted_primary_key,
      :define_method_attribute, :reset_primary_key, :primary_key=, :define_method_attribute=, :attribute_names, :initialize_generated_modules,
      :column_for_attribute, :define_attribute_methods, :undefine_attribute_methods, :instance_method_already_implemented?, :method_defined_within?,
      :dangerous_class_method?, :class_method_defined_within?, :attribute_method?, :has_attribute?, :generated_attribute_methods,
      :attribute_method_prefix, :attribute_method_suffix, :attribute_method_affix, :attribute_alias?, :attribute_alias, :define_attribute_method,
      :update_counters, :locking_enabled?, :locking_column, :locking_column=, :reset_locking_column, :decorate_attribute_type,
      :decorate_matching_attribute_types, :attribute, :define_attribute, :reset_counters, :increment_counter, :decrement_counter,
      :validates_absence_of, :validates_length_of, :validates_size_of, :validates_presence_of, :validates_associated, :validates_uniqueness_of,
      :validates_acceptance_of, :validates_confirmation_of, :validates_exclusion_of, :validates_format_of, :validates_inclusion_of,
      :validates_numericality_of, :define_callbacks, :normalize_callback_params, :__update_callbacks, :get_callbacks, :set_callback,
      :set_callbacks, :skip_callback, :reset_callbacks, :deprecated_false_terminator, :define_model_callbacks, :validate, :validators,
      :validates_each, :validates_with, :clear_validators!, :validators_on, :validates, :_validates_default_keys, :_parse_validates_options,
      :validates!, :_to_partial_path, :sanitize, :sanitize_sql, :sanitize_conditions, :quote_value, :sanitize_sql_for_conditions, :sanitize_sql_array,
      :sanitize_sql_for_assignment, :sanitize_sql_hash_for_assignment, :sanitize_sql_for_order, :expand_hash_conditions_for_aggregates, :sanitize_sql_like,
      :replace_named_bind_variables, :replace_bind_variables, :raise_if_bind_arity_mismatch, :replace_bind_variable, :quote_bound_value, :all,
      :default_scoped, :valid_scope_name?, :scope_attributes?, :before_remove_const, :ignore_default_scope?, :unscoped, :build_default_scope,
      :evaluate_default_scope, :ignore_default_scope=, :current_scope, :current_scope=, :scope_attributes, :base_class, :abstract_class?,
      :finder_needs_type_condition?, :sti_name, :descends_from_active_record?, :abstract_class, :compute_type, :abstract_class=, :table_name, :columns,
      :table_exists?, :columns_hash, :column_names, :attribute_types, :prefetch_primary_key?, :sequence_name, :quoted_table_name, :_default_attributes,
      :type_for_attribute, :inheritance_column, :attributes_builder, :inheritance_column=, :reset_table_name, :table_name=, :reset_column_information,
      :full_table_name_prefix, :full_table_name_suffix, :reset_sequence_name, :sequence_name=, :next_sequence_value, :column_defaults, :content_columns,
      :readonly_attributes, :attr_readonly, :create, :create!, :instantiate, :find, :type_caster, :arel_table, :find_by, :find_by!, :initialize_find_by_cache,
      :generated_association_methods, :arel_engine, :arel_attribute, :predicate_builder, :collection_cache_key, :relation_delegate_class,
      :initialize_relation_delegate_cache, :enum, :collecting_queries_for_explain, :exec_explain, :i18n_scope, :lookup_ancestors, :human_attribute_name,
      :references, :uniq, :maximum, :none, :exists?, :second, :limit, :order, :eager_load, :update, :delete_all, :destroy, :ids, :many?, :pluck, :third,
      :delete, :fourth, :fifth, :forty_two, :second_to_last, :third_to_last, :preload, :sum, :take!, :first!, :last!, :second!, :offset, :select, :fourth!,
      :third!, :third_to_last!, :fifth!, :where, :first_or_create, :second_to_last!, :forty_two!, :first, :having, :any?, :one?, :none?, :find_or_create_by,
      :from, :first_or_create!, :first_or_initialize, :except, :find_or_create_by!, :find_or_initialize_by, :includes, :destroy_all, :update_all, :or,
      :find_in_batches, :take, :joins, :find_each, :last, :in_batches, :reorder, :group, :left_joins, :left_outer_joins, :rewhere, :readonly, :create_with,
      :distinct, :unscope, :calculate, :average, :count_by_sql, :minimum, :lock, :find_by_sql, :count, :cache, :uncached, :connection, :connection_pool,
      :establish_connection, :connected?, :clear_cache!, :clear_reloadable_connections!, :connection_id, :connection_config, :clear_all_connections!,
      :remove_connection, :connection_specification_name, :connection_specification_name=, :retrieve_connection, :connection_id=, :clear_active_connections!,
      :sqlite3_connection, :direct_descendants, :benchmark, :model_name, :with_options, :attr_protected, :attr_accessible
    ]

    def method_missing(name, *args, &block)
      if args.count == 1 && name.start_with?("find_by_") && !block
        find_by(name.sub(/^find_by_/, "") => args[0])
      elsif !SERVER_METHODS.include?(name)
        raise "#{self.name}.#{name}(#{args}) (called class method missing)"
      end
    end

    def abstract_class=(val)
      @abstract_class = val
    end

    def scope(name, body)
      singleton_class.send(:define_method, name) do | *args |
        args = (args.count == 0) ? name : [name, *args]
        ReactiveRecord::Base.class_scopes(self)[args] ||= ReactiveRecord::Collection.new(self, nil, nil, self, args)
      end
      singleton_class.send(:define_method, "#{name}=") do |collection|
        ReactiveRecord::Base.class_scopes(self)[name] = collection
      end
    end

    def all
      ReactiveRecord::Base.class_scopes(self)[:all] ||= ReactiveRecord::Collection.new(self, nil, nil, self, "all")
    end

    def all=(collection)
      ReactiveRecord::Base.class_scopes(self)[:all] = collection
    end

    [:belongs_to, :has_many, :has_one].each do |macro|
      define_method(macro) do |*args| # is this a bug in opal?  saying name, scope=nil, opts={} does not work!
        name = args.first
        define_method(name) { @backing_record.reactive_get!(name, nil) }
        define_method("#{name}=") do |val|
          @backing_record.reactive_set!(name, backing_record.convert(name, val).itself)
        end
        opts = (args.count > 1 and args.last.is_a? Hash) ? args.last : {}
        Associations::AssociationReflection.new(self, macro, name, opts)
      end
    end

    def composed_of(name, opts = {})
      Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts)
      define_method(name) { @backing_record.reactive_get!(name, nil) }
      define_method("#{name}=") do |val|
        @backing_record.reactive_set!(name, backing_record.convert(name, val))
      end
    end

    def column_names
      ReactiveRecord::Base.public_columns_hash.keys
    end

    def columns_hash
      ReactiveRecord::Base.public_columns_hash[name] || {}
    end

    def server_methods
      @server_methods ||= {}
    end

    def server_method(name, default: nil)
      server_methods[name] = { default: default }
      define_method(name) do |*args|
        vector = args.count.zero? ? name : [[name]+args]
        @backing_record.reactive_get!(vector, nil)
      end
      define_method("#{name}!") do |*args|
        vector = args.count.zero? ? name : [[name]+args]
        @backing_record.reactive_get!(vector, true)
      end
    end

    def define_attribute_methods
      columns_hash.keys.each do |name|
        next if name == :id
        define_method(name) { @backing_record.reactive_get!(name, nil) }
        define_method("#{name}!") { @backing_record.reactive_get!(name, true) }
        define_method("#{name}=") do |val|
          @backing_record.reactive_set!(name, backing_record.convert(name, val))
        end
        define_method("#{name}_changed?") { @backing_record.changed?(name) }
      end
    end

    def _react_param_conversion(param, opt = nil)
      param = Native(param)
      param = JSON.from_object(param.to_n) if param.is_a? Native::Object
      result = if param.is_a? self
        param
      elsif param.is_a? Hash
        if opt == :validate_only
          klass = ReactiveRecord::Base.infer_type_from_hash(self, param)
          klass == self or klass < self
        else
          if param[primary_key]
            target = find(param[primary_key])
          else
            target = new
          end
          associations = reflect_on_all_associations
          param = param.collect do |key, value|
            assoc = reflect_on_all_associations.detect do |assoc|
              assoc.association_foreign_key == key
            end
            if assoc
              if value
                [assoc.attribute, {id: [value], type: [nil]}]
              else
                [assoc.attribute, [nil]]
              end
            else
              [key, [value]]
            end
          end

          # We do want to be doing something like this, but this breaks other stuff...
          #
          # ReactiveRecord::Base.load_data do
          #   ReactiveRecord::ServerDataCache.load_from_json(Hash[param], target)
          # end

          ReactiveRecord::ServerDataCache.load_from_json(Hash[param], target)
          target
        end
      else
        nil
      end
      result
    end

  end

end