# $Id: utils.rb 269 2003-11-07 15:54:17Z bolzer $
# Author:: Oliver M. Bolzer (mailto:oliver@fakeroot.net)
# Copyright:: (c) Oliver M. Bolzer, 2003
# Licence:: Ruby licence.
#
# :nodoc:
#
# Misc simple Utility-Classes for Vapor,
# so trival, that they have no extra tests

require 'vapor/persistable'

module Vapor
  
  # A persistent attribute of a Persistable class
  class ClassAttribute #:nodoc:
    Reference = 0
    String =    1
    Integer =   2
    Date =      3
    Float =     4
    Boolean =   5

    # create ClassAttribute
    def initialize( name, type, is_array )

      @name = name.to_s
      @is_array = is_array
      
      case type
      when 'String', ClassAttribute::String
        @type = ClassAttribute::String
      when 'Reference', ClassAttribute::Reference
        @type = ClassAttribute::Reference
      when 'Integer', ClassAttribute::Integer
        @type = ClassAttribute::Integer
      when 'Date', ClassAttribute::Date
        @type = ClassAttribute::Date
      else
        @type = ClassAttribute::String
      end

    end # initialize()

    attr_reader :name
    attr_reader :type
    attr_reader :is_array

    # the type in string representation
    def type_s
      case type
      when Reference
	"Reference"
      when String
        "String"
      when Integer
        "Integer"
      when Date
        "Date"
      when Float
        "Float"
      when Boolean
        "Boolean"
      end

    end # type_s

    # the type converted to a SQL type
    def type_sql
      sql_type =
        case type
        when Reference
          "BIGINT"
        when String
          "TEXT"
        when Integer
          "BIGINT"
        when Date
          "TIMESTAMP"
        when Float
          "REAL"
        when Boolean
          "BOOLEAN"
        end

      sql_type += "[]" if is_array

      sql_type
    end # type_sql

    # equivalence operator
    def ==( other )
      if self.name == other.name and self.type == other.type and self.is_array == other.is_array then
        return true
      else
        return false
      end
    end # ==()
    
  end # class ClassAttribute

  # metadata for a Persistable class
  class ClassMetaData #:nodoc:
    def initialize( name, superclass, table )
      @name = name
      @superclass = superclass || ""
      @table = table
      @attributes = Array.new
      @indexes = Array.new
      @unique = Array.new
    end

    attr_reader :name, :superclass, :table, :attributes, :indexes, :unique
  end # class ClassMetaData
  
  # a statement, forming a partial or whole search query
  class QueryStatement #:nodoc:
    private_class_method :new
  end # class QueryStatement

  # a basic query statement, consisting only of a single constraint 
  class BasicQueryStatement < QueryStatement #:nodoc:

    public_class_method :new

    # create a new QueryExpression for, made up of an attribute name,
    # a comparison operator and an match candidate
    def initialize( fieldname, operator, value )
      raise TypeError, "unsupported operator" unless ['=','~'].include?( operator )
      @fieldname = fieldname.to_s
      @operator = operator
      @value = value
      
    end # initialize()
    attr_reader :fieldname, :operator, :value

    def to_s
      [ @fieldname.to_s, @operator.to_s, @value.to_s ].join( ' ' )
    end # to_s()

  end # class BasicQueryStatement 

  # compex query statement makde up of two statements and an conditional
  # operator
  class ComplexQueryStatement < QueryStatement #:nodoc:
    
    public_class_method :new

    # create a new ComplexQueryStatement, containing two statements 
    # and a condiditonal operator
    def initialize( stmnt1, condition, stmnt2 )
      raise TypeError, "both statements must be QueryStatements" unless stmnt1.is_a? QueryStatement and stmnt2.is_a? QueryStatement
      raise TypeError, "unknown condition #{condition}" unless condition == 'AND'

      @statement1 = stmnt1
      @statement2 = stmnt2
      @condition = condition.to_s
    end # initialize()
    attr_reader :statement1, :statement2, :condition

    def to_s
      [ @statement1.to_s, @condition.to_s, @statement2.to_s ].join( ' ' )
    end # to_s

  end # class ComplexQueryStatement

  # Log entry containing information about a single Transaction.
  # Should not be instantiated by the user. Call only documented
  # get-methods.
  class TransactionLog
    include Vapor::Persistable

    # Metadata to describe the class' persistent attributes
    def self.metadata #:nodoc:
      @metadata = ClassMetaData.new( "Vapor::TransactionLog", "", "Vapor::TransactionLog" )
      @metadata.attributes << ClassAttribute.new( "committer", ClassAttribute::String, false )
      @metadata.attributes << ClassAttribute.new( "message", ClassAttribute::String, false )
      @metadata.attributes << ClassAttribute.new( "modified_objects", ClassAttribute::Reference, true )
      @metadata.attributes << ClassAttribute.new( "commit_time", ClassAttribute::Date, false )
      return @metadata.attributes
    end # self.metadata()
    
    # Create a new entry for the Transaction Log. 
    def initialize( committer = "", message = "" ) #:nodoc:
      @committer = committer
      @message = message
      @modified_objects = Array.new
      @commit_time = Time.now
    end # initialize()

    # Committer of the transaction.
    attr_accessor :committer
    # Log message describing the changes made by the transaction 
    attr_accessor :message

    # timestamp of the transaction's commit
    def date
      @commit_time.dup.freeze
    end
    # Frozen array of objects modified by the transaction.
    def modified_objects
      @modified_objects.dup.freeze
    end # modified_objects()

    # Add an object to the list of modified objets.
    def object_modified( obj ) #:nodoc:
      raise TypeError, "object not a Persistable" unless obj.kind_of? Persistable

      # only add to list if it's not already there
      unless @modified_objects.include? obj
        @modified_objects << obj
      end
    end # object_modified()
  
    # Remove PERSISTENT objects and self from the list of modofied objects.
    def cleanup_modofied_objects #:nodoc:
      @modified_objects.reject!{|obj|
        obj.state == Persistable::PERSISTENT or obj == self
      }
    end # cleanup_modofied_objects()
    
    # Update commit time to now.
    def update_commit_time #:nodoc:
      @commit_time = Time.now
    end # update_commit_time()

  end # class TransactionLog

end # module Vapor
