module TaliaCore

  # Class for the collection of elements returned by the "semantic accessor" methods
  # of a source (e.g. source[N::RDF.somethink])
  #
  # Each wrapper contains the values for one predicate of one source
  # (that is, for all triples of the form <thesource> <thepredicate> ?object). 
  #
  # The wrapper will lazy-load the data and only do a query to the database once
  # the items are actually requested. If a database request is necessary, all data
  # will be fetched in a single request.
  #
  # Modifications of the wrapper will happen in memory. Only when the wrapper is saved
  # using #save_items! will the modifications be written to the data store. 
  # #save_items! will be called by the "owning" source of this wrapper when the source
  # is being saved.
  #
  # Some of the methods work on the _values_, and other on the _objects_ of the 
  # collection. See SemanticCollectionItem#value and SemanticCollectionItem#object for
  # more on that.
  class SemanticCollectionWrapper

    include Enumerable

    attr_reader :force_type

    # Simple hash that checks if a type if property requires "special" handling
    # This will cause the wrapper to accept ActiveSource relations and all
    # sources will be casted to the given type
    def self.special_types
      @special_types ||= {
        N::RDF.type.to_s => N::SourceClass
      }
    end

    # Initialize the collection with the given source and predicate. No database
    # will take place during creation of the object
    def initialize(source, predicate)
      @assoc_source = source
      @assoc_predicate = if(predicate.respond_to?(:uri))
        predicate.uri.to_s
      else
        predicate.to_s
      end
      @force_type = self.class.special_types[@assoc_predicate]
    end

    # Get the element _value_ at the given index. See also
    # SemanticCollectionItem#value
    def at(index)
      items.at(index).value if(items.at(index))
    end
    alias :[] :at

    # The first _value_ in the collection
    def first
      item = items.first
      item ? item.value : nil
    end

    # The last _value_ in the collection
    def last
      item = items.last
      item ? item.value : nil
    end

    # Gets the _object_ at the given index. See 
    # SemanticCollectionItem#object
    def get_item_at(index)
      items.at(index).object if(items.at(index))
    end

    # Iterates over each _value_ of the items in the relation.
    def each
      items.each { |item| yield(item.value) }
    end

    # Collect method for the semantic wrapper, iterating
    # over the _values_ of the collection
    def collect
      items.collect { |item| yield(item.value) }
    end

    # Iterates of each _object_ of the items in the relation. 
    def each_item
      items.each { |item| yield(item.object) }
    end

    # Returns an array with all _values_ in the collection
    def values
      items.collect { |item| item.value }
    end

    # Returns only the _values_ of the given language.
    # (At the moment this is not aware of region codes or any
    # specialities, it just does a string matching)
    #
    # If no values with the given locale are found, this will
    # fall back on the default locale and then to the values
    # that don't have a locale at all.
    def values_with_lang(language = 'en')
      language_is_default = (language == I18n.default_locale.to_s)
      real = []
      default = []
      unset = []
      items.each do |item|
        # FIXME: At the moment, this only works for value attributes, not for 
        # sources
        if((val = item.value).respond_to?(:lang))
          real << val if(val.lang == language)
          default << val if(!language_is_default && (val.lang == I18n.default_locale.to_s))
          unset << val if(val.lang.blank?)
        else
          default << val
        end
      end
      return real unless(real.empty?)
      return default unless(default.empty?)
      unset
    end
    
    # Size of the collection
    def size
      return items.size if(loaded?)
      if(@items)
        # This is not really possible without loading, so we do it
        load!
        items.size
      else
        SemanticRelation.count(:conditions => {
          'subject_id' => @assoc_source.id,
          'predicate_uri' => @assoc_predicate })
        end
      end

      # Joins the _values_ of the colle ction into a string
      def join(join_str = ', ')
        strs = items.collect { |item| item.value.to_s }
        strs.join(join_str)
      end

      # Index of the given _value_
      def index(value)
        items.index(value)
      end

      # Check if the collection includes the _value_ given
      def include?(value)
        items.include?(value)
      end
      
      # Creates a record for a value and adds it. This will add the given value if it is
      # a database record and otherwise create a property with the given value.
      #
      # If a block is given, it will be called with the new element after the new element
      # has been added to the collection. If value is a collection, the block will be
      # called for each element of the collection.
      #
      # The order, if not nil, can be used to have a fixed order of SemanticRelation
      # records. This is mainly used by the Collection class
      def add_record(value, order = nil)
        raise(ArgumentError, "Blank value assigned") if(value.blank? && !value.is_a?(Enumerable))
        # We use order exclusively for "ordering" predicates
        assit_equal(TaliaCore::Collection.index_to_predicate(order), @assoc_predicate) if(order)

        value = [ value ] unless(value.kind_of?(Array))

        value.each do |val|
          rel = create_predicate(val)
          rel.rel_order = order if(order)
          block_given? ? yield(rel) : insert_item(rel)
        end
      end
      alias_method '<<', :add_record
      alias_method :concat, :add_record

      # Replace a value with a new one. Equivalent to removing the old value
      # and adding the new one
      def replace_value(old_value, new_value)
        idx = items.index(old_value)
        items[idx].destroy
        # Creates a new relation and adds it in the place of the old one
        add_record(new_value) { |new_item| items[idx] = new_item }
      end
      
      # Replace the contents of the current wrapper with the values passed. 
      # Blank values are ignored by this method. If non new values are passed 
      # (or all values are blank), this will simply 
      def replace(*new_values)
        new_values.flatten! if(new_values.first.is_a?(Array)) # Flatten if used as #replace([a, b, c])
        new_values.reject! { |v| v.blank? }
        new_values.collect! { |v| create_predicate(v) }
        remaining_items = []      
        items.each do |item|
          if(new_values.include?(item))
            remaining_items << item
            new_values.delete(item)
          else
            item.destroy
          end
        end
        @items = remaining_items + new_values
      end

      # Remove the given value. With no parameters, the whole list will be
      # cleared and the RDF will be updated immediately (!).
      def remove(*params)
        if(params.length > 0)
          params.each { |par| remove_relation(par) }
        else
          if(loaded?)
            items.each { |item| item.destroy }
          else
            SemanticRelation.destroy_all(
            :subject_id => @assoc_source.id,
            :predicate_uri => @assoc_predicate
            )
          end
          @assoc_source.my_rdf.remove(@assoc_predicate.to_uri) unless(@assoc_source.uri.to_s.blank?)
          @items = []
          @loaded = true
        end
      end

      # This attempts to save the items to the database. This will do nothing if
      # the collection was never loaded to memory. It also tries to ignore data
      # that is known to already exist in the data store and only write the records
      # could actually have been modified.
      def save_items!
        return if(clean?) # If there are no items, nothing was modified
        @assoc_source.save! unless(@assoc_source.id)
        @items.each do |item|
          item.save!
        end
        @items = nil unless(loaded?) # Otherwise we'll have trouble reload-and merging
      end

      # Indicates of the internal collection is loaded
      def loaded?
        @loaded
      end

      # Indicates that the wraper is "clean", that is it hasn't been written to
      # or read from
      def clean?
        @items.nil?
      end

      def empty?
        self.size == 0
      end
      
      # Forces this relation to be empty. This initializes the relation,
      # assuming that no data exists in the database. The collection will
      # be empty, and the database will *not* be queried.
      #
      # *Warning* Only call this if you need an empty wrapper
      # and you are sure that there are no corresponding values in the database
      def init_as_empty!
        raise(ArgumentError, "Already initialized!") if(loaded?)
        @items = []
        @loaded = true
      end

      # Insert a new relation directly. To be used with care!
      def insert_item(item) # :nodoc:
        raise(ArgumentError, "Can only insert a SemanticRelation") unless(item.is_a?(SemanticRelation))
        raise(ArgumentError, "New relation does not match the predicate of the wrapper") if(item.predicate_uri != @assoc_predicate.to_s)
        @items ||= []
        @items << item
      end

      private

      # Load the current collection from the database.
      def load!
        # Check if there are records that have been added previously
        relations = SemanticRelation.find(:all, 
          :conditions => { :subject_id => @assoc_source.id, :predicate_uri => @assoc_predicate.to_s }, 
          :include => [:subject, :object])
        @items ||= []
        @loaded = true
        @items = (relations | @items)
      end

      # Returns the items in the collection. These are the SemanticCollectionItem
      # objects
      def items
        load! unless(loaded?)
        @items
      end

      # Deletes the relation where with the current predicate and the given 
      # value.
      def remove_relation(value)
        idx = items.index(value)
        return unless(idx)
        remove_at(idx)
      end

      # Removes a relation at the given index
      def remove_at(index)
        items.at(index).destroy
        items.delete_at(index)
      end

      # Creates a new semantic relation with the given value and the subject
      # and predicate taken from the collection. The value will be converted
      # into an ActiveSource or SemanticProperty as appropriate and used as
      # the object of the new SemanticRelation
      def create_predicate(value)
        # TODO: Semantic Properties should only be created inside, since assigning
        #       one to multiple relations and then deleting breaks integrity.
        #       The whole semantic property should be flattened into a field in
        #       SemanticRelation anyway.
        assit(!value.is_a?(SemanticProperty), "Should not pass in Semantic Properties here!")
        # We need to manually create the relation, to add the predicate_url
        to_add = SemanticRelation.new(
        :subject => @assoc_source,
        :predicate_uri => @assoc_predicate, 
        :object => create_object_value(value)
        ) 
      end
      
      # Creates the "object value" which, for the given value, will be used as
      # the object for the SemanticRelation.
      def create_object_value(value)
        if(@force_type)
          # If we have the "force_type" option, we assume that every value
          # we get is a Resource/ActiveSource
          uri = value.respond_to?(:uri) ? value.uri : value
          ActiveSource.new(uri.to_s)
        elsif(value.is_a?(TaliaCore::ActiveSource) || value.is_a?(TaliaCore::SemanticProperty))
          value
        elsif(value.respond_to?(:uri)) # This appears to refer to a Source. We only add if we can find that source
          TaliaCore::ActiveSource.find(value.uri)
        elsif(prop_options[:type].is_a?(Class) && (prop_options[:type] <= TaliaCore::ActiveSource))
          TaliaCore::ActiveSource.find(value)
        else
          # Check if we need to add from a PropertyString
          propvalue = value.is_a?(PropertyString) ? value.to_rdf : value
          TaliaCore::SemanticProperty.new(:value => propvalue)
        end
      end
      
      # The options that were defined on the "owning" source with
      # singular_property, multi_property or property_options
      def prop_options
        @assoc_source.property_options_for(@assoc_predicate)
      end

    end

  end