require 'volt/models/persistors/store'
require 'volt/models/persistors/query/query_listener_pool'
require 'volt/models/persistors/store_state'

module Persistors
  class ArrayStore < Store
    include StoreState

    @@query_pool = QueryListenerPool.new

    attr_reader :model

    def self.query_pool
      @@query_pool
    end

    def initialize(model, tasks=nil)
      super

      @query = @model.options[:query]
    end

    def event_added(event, first, first_for_event)
      # First event, we load the data.
      if first
        @has_events = true
        load_data
      end
    end

    def event_removed(event, last, last_for_event)
      # Remove listener where there are no more events on this model
      if last
        @has_events = false
        stop_listening
      end
    end

    # Called when an event is removed and we no longer want to keep in
    # sync with the database.
    def stop_listening(stop_watching_query=true)
      return if @has_events
      return if @fetch_promises && @fetch_promises.size > 0

      @query_computation.stop if @query_computation && stop_watching_query

      if @query_listener
        @query_listener.remove_store(self)
        @query_listener = nil
      end

      @state = :dirty
    end

    # Called the first time data is requested from this collection
    def load_data
      # Don't load data from any queried
      if @state == :not_loaded || @state == :dirty
        # puts "Load Data at #{@model.path.inspect} - query: #{@query.inspect} on #{self.inspect}"
        change_state_to :loading

        if @query.is_a?(Proc)
          @query_computation = -> do
            puts "Run Query Again"
            stop_listening(false)

            change_state_to :loading

            run_query(@model, @query.call)
          end.watch!
        else
          run_query(@model, @query)
        end
      end
    end

    # Clear out the models data, since we're not listening anymore.
    def unload_data
      change_state_to :not_loaded
      @model.clear
    end

    def run_query(model, query={})
      @model.clear

      collection = model.path.last
      # Scope to the parent
      if model.path.size > 1
        parent = model.parent

        parent.persistor.ensure_setup if parent.persistor

        if parent && (attrs = parent.attributes) && attrs[:_id].true?
          query[:"#{model.path[-3].singularize}_id"] = attrs[:_id]
        end
      end

      @query_listener = @@query_pool.lookup(collection, query) do
        # Create if it does not exist
        QueryListener.new(@@query_pool, @tasks, collection, query)
      end

      @query_listener.add_store(self)
    end

    # Find can take either a query object, or a block that returns a query object.  Use
    # the block style if you need reactive updating queries
    def find(query=nil, &block)
      # Set a default query if there is no block
      if block
        if query
          raise "Query should not be passed in to a find if a block is specified"
        end
        query = block
      else
        query ||= {}
      end

      return Cursor.new([], @model.options.merge(:query => query))
    end

    # Returns a promise that is resolved/rejected when the query is complete.  Any
    # passed block will be passed to the promises then.  Then will be passed the model.
    def then(&block)
      promise = Promise.new

      promise = promise.then(&block) if block

      if @state == :loaded
        promise.resolve(@model)
      else
        @fetch_promises ||= []
        @fetch_promises << promise

        load_data
      end

      return promise
    end

    # Called from backend
    def add(index, data)
      $loading_models = true

      # Don't add if the model is already in the ArrayModel
      if !@model.array.find {|v| v._id == data[:_id] }
        # Find the existing model, or create one
        new_model = @@identity_map.find(data[:_id]) do
          new_options = @model.options.merge(path: @model.path + [:[]], parent: @model)
          @model.new_model(data, new_options, :loaded)
        end

        @model.insert(index, new_model)
      end

      $loading_models = false
    end

    def remove(ids)
      $loading_models = true
      ids.each do |id|
        # TODO: optimize this delete so we don't need to loop
        @model.each_with_index do |model, index|
          if model._id == id
            del = @model.delete_at(index)
            break
          end
        end
      end

      $loading_models = false
    end

    def channel_name
      @model.path[-1]
    end

    # When a model is added to this collection, we call its "changed"
    # method.  This should trigger a save.
    def added(model, index)
      if model.persistor
        # Tell the persistor it was added
        model.persistor.add_to_collection
      end
    end

    def removed(model)
      if model.persistor
        # Tell the persistor it was removed
        model.persistor.remove_from_collection
      end

      if defined?($loading_models) && $loading_models
        return
      else
        StoreTasks.delete(channel_name, model.attributes[:_id])
      end
    end

  end
end