# this is a mock-up.  No persistence, no parallelism, no optimizations,
# merely intended to demonstrate views.

class View
  def initialize(value)
    @map_output = {}
    @reduce_input = {}
    instance_eval(value)
    @data = {}
  end

  def get(key)
    @data[key]
  end

  def update(id, value)
    old_map = @map_output[id.to_s]
    new_map = self.map(id, value)

    schedule = []

    # out with the old
    (old_map.to_a - new_map.to_a).each do |key, value|
      @reduce_input[key].delete_at(@reduce_input[key].index(value))
      @reduce_input.delete(key) if @reduce_input[key].empty?
      schedule << key
    end

    # in with the new
    (new_map.to_a - old_map.to_a).each do |key, value|
      @reduce_input[key] ||= []
      @reduce_input[key] << value
      schedule << key unless schedule.index(key)
    end

    # update intermediate tables
    @map_output[id] = new_map
    schedule.each do |key|
      if @reduce_input[key]
        @data[key] = reduce(key, @reduce_input[key])
      else
        @data.delete(key)
      end
    end
  end
end

class CouchDB
  def initialize(name)
    @data = {}
    @views = {}
  end

  def get(id)
    segments = id.split('/')
    if segments.length == 1
      @data[id]
    else
      @views[segments.first].get(segments.last)
    end
  end

  def post(id, value)
    id.chop! if id[-1] == ?/
    @views[id.to_s] = View.new(value)

    # on a real database, this would be done in parallel
    @data.each_pair {|name,value| @views[id.to_s].update(name, value)}
  end

  def put(id, value)
    @data[id.to_s] = value
    @views.each_value {|view| view.update(id, value)}
  end

  def delete(id)
    @data.delete(id)
  end
end
