require 'Module_ext'
require 'dbi'

class Array
    def to_hash
        result = {}
        each { |entry| result[yield(entry)] = entry }
        result
    end
end


class DatabaseObject
    include Comparable

    def self.select_part(table_name = obj_table_name_field)
        "SELECT #{ keys.join(', ') } FROM #{table_name}"
    end

    def self.where_part(columns = self.keys) 
        key_names = columns.is_a?(Hash) ? columns.keys : columns
        key_names.collect { |key| "#{key} = ?" }.join(" AND ")
    end

    def select_statement(collection_type, *keys)
        "select #{ keys.join(', ') } from #{collection_type} #{ where_clause } "
    end

    def where_clause
        return "" if keys.length == 0
        "where " + keys.collect { |key| key + " = '" + eval(key) + "'" }.join(" AND ")
    end

    def subkeys(keys)
        keys - self.keys
    end


    def get_key(row, keys)
        result = subkeys(keys).collect { |key| row[key.upcase] }
        return result[0] if result.size == 1
        result
    end

    def key_values
        keys.collect { |key| self.send(key) }
    end

    def key_values=(values)
        keys.each { |key| self.instance_eval("@#{key} = values.shift") }
    end

    def property_values
        return {} if not respond_to? :__db_property_map
        __db_property_map
    end

    def property_values=(values)
        return if values.empty?

        @__db_property_map = values
        self.class.instance_eval("attr_reader :__db_property_map")
    end

    def collection_values
        self.class.collection_names.collect { |collection_name|
            self.send(collection_name)
        }
    end

    def collection_values=(values)
        self.class.collection_names.each { |collection_name|
            self.instance_eval("@#{collection_name} = values.shift")
            self.class.instance_eval("attr_reader :#{collection_name}")
        }
    end

    def <=>(other)
        keys.each { |key| return self.send(key) <=> other.send(key) if ((self.send(key) <=> other.send(key)) != 0) }
        return 0
    end

    def eql?(other)
        return self == other
    end

    def to_s
        self.class.name + "<" + keys.collect { |key| key + "=" + self.send(key).to_s }.join(',') + ">"
    end

    def hash
        result = 0
        keys.each { |key| result ^= self.send(key).hash }
        result
    end


    def _dump(depth)
        result = []
        result.push key_values
        result.push property_values
        result.push collection_values
 
        Marshal.dump(result, depth-1)
    end

    def load(str)
        ary = Marshal.load(str)
        
        self.key_values         = ary.shift
        self.property_values    = ary.shift
        self.collection_values  = ary.shift

        self
    end


    def self.collection_names
        []
    end

    def self.obj_table_name(name)
        module_eval <<-"end_eval"
            def self.obj_table_name_field
                "#{ name }"
            end

        end_eval
    end


  def self.no_key
    module_eval <<-"end_eval"
        def initialize(dbh)
            @dbh = dbh
        end
    end_eval

    module_eval <<-"end_eval"
        def keys
            [ ]
        end
    end_eval

    module_eval <<-"end_eval"
        class << self
            def keys
                [ ]
            end
        end
    end_eval
  end

  def self.make_key(*ids)
    module_eval <<-"end_eval"
        def initialize(dbh = nil, #{ ids.collect { |id| id.id2name + " = nil " }.join(", ") })
            @dbh = dbh
            #{ ids.collect { |id| "@" + id.id2name + " = " + id.id2name }.join("\n") }
            puts self.to_s if $debug_#{name}
        end
    end_eval

    module_eval <<-"end_eval"
        def keys
            [ #{ ids.collect { |id| '"' + id.id2name + '"' }.join(', ') } ]
        end
    end_eval

    module_eval <<-"end_eval"
        class << self
            def keys
                [ #{ ids.collect { |id| '"' + id.id2name + '"' }.join(', ') } ]
            end
        end
    end_eval

    for id in ids
        attr_accessor id
    end

    module_eval <<-"end_eval"

        def self._load(str)
            obj = self.new()
            obj.load(str)
        end
    end_eval
  end

  def self.internal_collection(collection_name, classname, select_eval_string, field_param_names = self.keys)
    module_eval <<-"end_eval"
        def #{collection_name}
            begin
                puts "#{select_eval_string}" if $debug_sql
                sth = @dbh.execute("#{select_eval_string}" #{field_param_names.collect { |col| ", " + col.to_s }.join })

                result = {}
                sth.each { |row| result[get_key(row, #{classname}.keys)] = #{classname}.new(@dbh, *row.to_ary) }
                result
            rescue
                $stderr.puts "SQL FAILED: " + $!
                $stderr.puts "Failed statement: #{select_eval_string}"
                $stderr.puts "Params: " #{field_param_names.collect { |col| " + ', ' + " + col.to_s }.join }
                raise
            end
        end

        once :#{collection_name}

        (@collection_names ||= []) << "#{collection_name}"
        def self.collection_names
            @collection_names
        end
    end_eval
  end


  def self.collection(collection, clz, tablename = nil)
    collection_name = collection.id2name
    classname = clz.id2name
    tablename = (tablename.nil?) ? "" : "'" + tablename + "'"
    select_eval_string = '#{' + classname + '.select_part(' + tablename + ')} WHERE #{self.class.where_part}'
    internal_collection(collection_name, classname, select_eval_string)
  end


  def self.collection_sql(collection, clz, sql)
    collection_name = collection.id2name
    classname = clz.id2name
    internal_collection(collection_name, classname, sql)
  end

  def self.collection_where(collection, clz, columns)
    collection_name = collection.id2name
    classname = clz.id2name

    key_names = (columns.is_a? Hash) ? columns.values : self.keys
    internal_collection(collection_name, classname, 
        '#{' + classname + '.select_part} WHERE ' + where_part(columns),
        key_names)
  end

  def self.db_properties(*ids)
    module_eval <<-"end_eval"
        def __db_property_map
            sql = select_statement("#{obj_table_name_field}", #{ ids.collect { |id| '"' + id.id2name + '"' }.join(", ") })
            begin
                puts sql if $debug_sql
                @dbh.select_one sql
            rescue
                $stderr.puts "SQL FAILED: " + $!
                $stderr.puts "Failed statement: " + sql
                raise
            end
        end
        once :__db_property_map
    end_eval

     
    for id in ids
       module_eval <<-"end_eval"
           def #{id.to_s}
               __db_property_map["#{id.to_s.upcase}"]
           end
       end_eval
    end
  end

end
