module Pbw
  class Token
    include ::Mongoid::Document
    include ::Mongoid::Timestamps
    field :name, type: String
    validates_presence_of :name

    belongs_to :area, :class_name => 'Pbw::Area'
    belongs_to :user, :class_name => 'Pbw::User'
    has_and_belongs_to_many :capabilities, :class_name => 'Pbw::Capability'
    has_and_belongs_to_many :constraints, :class_name => 'Pbw::Constraint'
    has_and_belongs_to_many :triggers, :class_name => 'Pbw::Trigger'
    has_many :attached_processes, :class_name => 'Pbw::AttachedProcess'
    has_many :item_containers, :class_name => 'Pbw::ItemContainer'

    attr_accessible :name

    def self.viewable_by?(user, subject)
        user.admin? || subject.user == user
    end

    def self.creatable_by?(user, subject)
        true
    end

    def self.editable_by?(user, subject)
        user.admin? || subject.user == user
    end

    def self.deletable_by?(user, subject)
        user.admin? || subject.user == user
    end

    def before_ownership(user)
        # stub method
        true
    end

    def after_ownership(user)
        # stub method
    end

    def before_move(area)
        # stub method
        true
    end

    def after_move(area)
        # stub method
    end

    def before_add_item(item, quantity)
        # stub method
        true
    end

    def after_add_item(item, quantity)
        # stub method
    end

    def before_remove_item(item, quantity)
        # stub method
        true
    end

    def after_remove_item(item, quantity)
        # stub method
    end

    def attach_tick_process(process, ticks_to_wait=0)
        AttachedProcess.create(token: self, process: process, tickable: true, ticks_waiting: ticks_to_wait)
    end

    def attach_update_process(process, updates_to_wait=0)
        AttachedProcess.create(token: self, process: process, updatable: true, updates_waiting: updates_to_wait)
    end

    def can_convert?(item)
        self.capabilities.any?{|c| c.can_convert?(item)}
    end

    def count_item(item)
        container = ItemContainer.find_for_token(self)
        container ? container.quantity : 0
    end

    def add_item!(item, quantity)
        return false unless quantity && quantity.responds_to?(:abs)
        return remove_item!(item, quantity.abs) if quantity < 0
        return false unless item && before_add_item(item,quantity)
        ItemContainer.find_or_create_for_token(self, item, quantity)
    end

    def remove_item!(item, quantity)
        return false unless quantity && quantity.responds_to?(:abs)
        return add_item!(item, quantity.abs) if quantity < 0
        return false unless item && before_remove_item(item,quantity)
        ItemContainer.find_or_create_for_token(self, item, (0 - quantity))
    end

    def set_ownership!(user)
        return false unless user && before_ownership(user)
        self.user = user
        if save
            after_ownership(user)
        end
        self.user
    end

    def move_to_area!(area)
        return false unless area && before_move(area) && (self.area.nil? || self.area.before_token_leaves(self)) && area.before_token_enters(self)
        old_area = self.area
        self.area = area
        if save
            after_move(area)
            old_area.after_token_leaves(self) if old_area
            area.after_token_enters(self)
        end
        self.area
    end

    def has_constraint?(constraint)
        constraint = Constraint.find(constraint) if constraint.is_a?(String)
        self.constraints.include?(constraint)
    end

    def add_constraint!(constraint)
        return false unless constraint && constraint.before_add(self)
        self.constraints << constraint
        save
        constraint.after_add(self)
        self
    end

    def remove_constraint!(constraint)
        return false unless constraint && constraint.before_remove(self)
        self.constraints.delete_if{|c| c.name == constraint.name}
        save
        constraint.after_remove(self)
        self
    end

    def has_capability?(capability)
        capability = Capability.find(capability) if capability.is_a?(String)
        self.capabilities.include?(capability)
    end

    def add_capability!(capability)
        return false unless capability && capability.before_add(self)
        self.capabilities << capability
        save
        capability.after_add(self)
        self
    end

    def remove_capability!(capability)
        return false unless capability && capability.before_remove(self)
        self.capabilities.delete_if{|c| c.name == capability.name}
        save
        capability.after_remove(self)
        self
    end

    def check_constraints_and_capabilities(&changeset)
        self.capabilities.any?{|c| !c.before_process(self, changeset)}
        self.constraints.any?{|c| !c.before_process(self, changeset)}
    end

    def check_triggers!
        self.triggers.each{|t| t.check! }
    end
  end
end