Dir.glob(File.join(File.dirname(__FILE__), *%w[combinators *.rb])) do |file|
  require file.sub(/\.rb$/, '')
end

module DeferrableGratification
  # Combinators for building up higher-level asynchronous abstractions by
  # composing simpler asynchronous operations, without having to manually wire
  # callbacks together and remember to propagate errors correctly.
  #
  # @example Perform a sequence of database queries and transform the result.
  #   # With DG::Combinators:
  #   def product_names_for_username(username)
  #     DB.query('SELECT id FROM users WHERE username = ?', username).bind! do |user_id|
  #       DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
  #     end.transform do |product_names|
  #       product_names.join(', ')
  #     end
  #   end
  #
  #   status = product_names_for_username('bob')
  #
  #   status.callback {|product_names| ... }
  #   # If both queries complete successfully, the callback receives the string
  #   # "Car, Spoon, Coffee".  The caller doesn't have to know that two separate
  #   # queries were made, or that the query result needed transforming into the
  #   # desired format: he just gets the event he cares about.
  #
  #   status.errback {|error| puts "Oh no!  #{error}" }
  #   # If either query went wrong, the errback receives the error that occurred.
  #
  #
  #   # Without DG::Combinators:
  #   def product_names_for_username(username)
  #     product_names_status = EM::DefaultDeferrable.new
  #     query1_status = DB.query('SELECT id FROM users WHERE username = ?', username)
  #     query1_status.callback do |user_id|
  #       query2_status = DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
  #       query2_status.callback do |product_names|
  #         product_names = product_names.join(', ')
  #         # N.B. don't forget to report success to the caller!
  #         product_names_status.succeed(product_names)
  #       end
  #       query2_status.errback do |error|
  #         # N.B. don't forget to tell the caller we failed!
  #         product_names_status.fail(error)
  #       end
  #     end
  #     query1_status.errback do |error|
  #       # N.B. don't forget to tell the caller we failed!
  #       product_names_status.fail(error)
  #     end
  #     # oh yes, and don't forget to return this!
  #     product_names_status
  #   end

  module Combinators
    # Alias for {#bind!}.
    #
    # Note that this takes a +Proc+ (e.g. a lambda) while {#bind!} takes a
    # block.
    #
    # @param [Proc] prok proc to call with the successful result of +self+.
    #   Assumed to return a Deferrable representing the status of its own
    #   operation.
    #
    # @return [Deferrable] status of the compound operation of passing the
    #     result of +self+ into the proc.
    #
    # @example Perform a database query that depends on the result of a previous query.
    #   DB.query('first query') >> lambda {|result| DB.query("query with #{result}") }
    def >>(prok)
      Bind.setup!(self, &prok)
    end

    # Register callbacks so that when this Deferrable succeeds, its result
    # will be passed to the block, which is assumed to return another
    # Deferrable representing the status of a second operation.
    #
    # If this operation fails, the block will not be run.  If either operation
    # fails, the compound Deferrable returned will fire its errbacks, meaning
    # callers don't have to know about the inner operations and can just
    # subscribe to the result of {#bind!}.
    #
    #
    # If you find yourself writing lots of nested {#bind!} calls, you can
    # equivalently rewrite them as a chain and remove the nesting: e.g.
    #
    #     a.bind! do |x|
    #       b(x).bind! do |y|
    #         c(y).bind! do |z|
    #           d(z)
    #         end
    #       end
    #     end
    #
    # has the same behaviour as
    #
    #     a.bind! do |x|
    #       b(x)
    #     end.bind! do |y|
    #       c(y)
    #     end.bind! do |z|
    #       d(y)
    #     end
    #
    # As well as being more readable due to avoiding left margin inflation,
    # this prevents introducing bugs due to inadvertent local variable capture
    # by the nested blocks.
    #
    #
    # @see #>>
    #
    # @param &block block to call with the successful result of +self+.
    #   Assumed to return a Deferrable representing the status of its own
    #   operation.
    #
    # @return [Deferrable] status of the compound operation of passing the
    #     result of +self+ into the block.
    #
    # @example Perform a web request based on the result of a database query.
    #   DB.query('url').bind! {|url| HTTP.get(url) }.
    #     callback {|response| puts "Got response!" }
    def bind!(&block)
      Bind.setup!(self, &block)
    end

    # Transform the result of this Deferrable by invoking +block+, returning
    # a Deferrable which succeeds with the transformed result.
    #
    # If this operation fails, the operation will not be run, and the returned
    # Deferrable will also fail.
    #
    # @param &block block that transforms the expected result of this
    #   operation in some way.
    #
    # @return [Deferrable] Deferrable that will succeed if this operation did,
    #   after transforming its result.
    #
    # @example Retrieve a web page and call back with its title.
    #   HTTP.request(url).transform {|page| Hpricot(page).at(:title).inner_html }
    def transform(&block)
      Bind.setup!(self, :without_chaining => true, &block)
    end

    # Transform the value passed to the errback of this Deferrable by invoking
    # +block+.  If this operation succeeds, the returned Deferrable will
    # succeed with the same value.  If this operation fails, the returned
    # Deferrable will fail with the transformed error value.
    #
    # @param &block block that transforms the expected error value of this
    #   operation in some way.
    #
    # @return [Deferrable] Deferrable that will succeed if this operation did,
    #   otherwise fail after transforming the error value with which this
    #   operation failed.
    def transform_error(&block)
      errback do |*err|
        self.fail(
          begin
            yield(*err)
          rescue => e
            e
          end)
      end
    end

    # If this Deferrable succeeds, ensure that the arguments passed to
    # +Deferrable#succeed+ meet certain criteria (specified by passing a
    # predicate as a block).  If they do, subsequently defined callbacks will
    # fire as normal, receiving the same arguments; if they do not, this
    # Deferrable will fail instead, calling its errbacks with a {GuardFailed}
    # exception.
    #
    # This follows the usual Deferrable semantics of calling +Deferrable#fail+
    # inside a callback: any callbacks defined *before* the call to {#guard}
    # will still execute as normal, but those defined *after* the call to
    # {#guard} will only execute if the predicate returns truthy.
    #
    # Multiple successive calls to {#guard} will work as expected: the
    # predicates will be evaluated in order, stopping as soon as any of them
    # returns falsy, and subsequent callbacks will fire only if all the
    # predicates pass.
    #
    # If instead of returning a boolean, the predicate raises an exception,
    # the Deferrable will fail, but errbacks will receive the exception raised
    # instead of {GuardFailed}.  You could use this to indicate the reason for
    # failure in a complex guard expression; however the same intent might be
    # more clearly expressed by multiple guard expressions with appropriate
    # reason messages.
    #
    # @param [String] reason optional description of the reason for the guard:
    #                        specifying this will both serve as code
    #                        documentation, and be included in the
    #                        {GuardFailed} exception for error handling
    #                        purposes.
    #
    # @yieldparam *args the arguments passed to callbacks if this Deferrable
    #                   succeeds.
    # @yieldreturn [Boolean] +true+ if subsequent callbacks should fire (with
    #                        the same arguments); +false+ if instead errbacks
    #                        should fire.
    #
    # @raise [ArgumentError] if called without a predicate
    def guard(reason = nil, &block)
      raise ArgumentError, 'must be called with a block' unless block_given?
      callback do |*callback_args|
        begin
          unless block.call(*callback_args)
            raise ::DeferrableGratification::GuardFailed.new(reason, callback_args)
          end
        rescue => exception
          fail(exception)
        end
      end
      self
    end

    # Boilerplate hook to extend {ClassMethods}.
    def self.included(base)
      base.send :extend, ClassMethods
    end

    # Combinators which don't make sense as methods on +Deferrable+.
    #
    # {DeferrableGratification} extends this module, and thus the methods
    # here are accessible via the {DG} alias.
    module ClassMethods
      # Execute a sequence of asynchronous operations that may each depend on
      # the result of the previous operation.
      #
      # @see #bind! more detail on the semantics.
      #
      # @param [*Proc] *actions procs that will perform an operation and
      #   return a Deferrable.
      #
      # @return [Deferrable] Deferrable that will succeed if all of the
      #   chained operations succeeded, and callback with the result of the
      #   last operation.
      def chain(*actions)
        actions.inject(DG.const(nil), &:>>)
      end

      # Combinator that waits for all of the supplied asynchronous operations
      # to succeed or fail, then succeeds with the results of all those
      # operations that were successful.
      #
      # This Deferrable will never fail.  It may also never succeed, if _any_
      # of the supplied operations does not either succeed or fail.
      #
      # The successful results are guaranteed to be in the same order as the
      # operations were passed in (which may _not_ be the same as the
      # chronological order in which they succeeded).
      #
      # @param [*Deferrable] *operations deferred statuses of asynchronous
      #   operations to wait for.
      #
      # @return [Deferrable] a deferred status that will succeed after all the
      #   +operations+ have either succeeded or failed; its callbacks will be
      #   passed an +Enumerable+ containing the results of those operations
      #   that succeeded.
      def join_successes(*operations)
        Join::Successes.setup!(*operations)
      end

      # Combinator that waits for any of the supplied asynchronous operations
      # to succeed, and succeeds with the result of the first (chronologically)
      # to do so.
      #
      # This Deferrable will fail if all the operations fail.  It may never
      # succeed or fail, if one of the operations also does not.
      #
      # @param (see #join_successes)
      #
      # @return [Deferrable] a deferred status that will succeed as soon as any
      #   of the +operations+ succeeds; its callbacks will be passed the result
      #   of that operation.
      def join_first_success(*operations)
        Join::FirstSuccess.setup!(*operations)
      end

      # Combinator that repeatedly executes the supplied block until it
      # succeeds, then succeeds itself with the eventual result.
      #
      # This Deferrable may never succeed, if the operation never succeeds.
      # It will fail if an iteration raises an exception.
      #
      # @note this combinator is intended for use inside EventMachine.  It will
      #   still work outside of EventMachine, _provided_ that the operation is
      #   synchronous (although a simple +while+ loop might be preferable in
      #   this case!).
      #
      # @param loop_deferrable for internal use only, always omit this.
      # @param &block operation to execute until it succeeds.
      #
      # @yieldreturn [Deferrable] deferred status of the operation.  If it
      #   fails, the operation will be retried.  If it succeeds, the combinator
      #   will succeed with the result.
      #
      # @return [Deferrable] a deferred status that will succeed once the
      #   supplied operation eventually succeeds.
      def loop_until_success(loop_deferrable = DefaultDeferrable.new, &block)
        if EM.reactor_running?
          EM.next_tick do
            begin
              attempt = yield
            rescue => e
              loop_deferrable.fail(e)
            else
              attempt.callback(&loop_deferrable.method(:succeed))
              attempt.errback { loop_until_success(loop_deferrable, &block) }
            end
          end
        else
          # In the synchronous case, we could simply use the same
          # implementation as in EM, but without the next_tick; unfortunately
          # that means direct recursion, so risks stack overflow.  Instead we
          # just reimplement as a loop.
          results = []
          begin
            yield.callback {|*values| results << values } while results.empty?
          rescue => e
            loop_deferrable.fail(e)
          else
            loop_deferrable.succeed(*results[0])
          end
        end
        loop_deferrable
      end
    end
  end
end


# -*- encoding: utf-8 -*-
module TaskManager
  class Plan < ActiveRecord::Base
    include TaskManager::DeadlineCalculator
    extend Enumerize

    has_many :assignables, as: :target
    has_many :callables, as: :target

    enumerize :plan_type, in: [:daily, :weekly, :monthly, :quarterly, :yearly]

    serialize :data, ActiveRecord::Coders::Hstore

    accepts_nested_attributes_for :assignables, :callables

    attr_accessible :autocompletable, :data, :last_task_created_at,
      :name, :plan_type, :begin_to_remind, :enabled_at,
      :assignables_attributes, :callables_attributes

    validates_presence_of :name, :plan_type, :begin_to_remind,
      :enabled_at, :assignables
    validates_uniqueness_of :name
    validates_numericality_of :begin_to_remind, greater_than_or_equal_to: 0

    # validate the deadline
    validates :data, presence: true, deadline: true

    def assignees
      assignables.collect(&:assignee)
    end

    def callbacks
      callables.collect(&:callback)
    end

    # 生成计划任务
    #
    # 为每一个计划的执行者创建一个计划任务。
    def generate_tasks
      tasks = []
      Plan.transaction do
        assignables.each do |a|
          tasks << generate_task_for_assignable(a)
        end
      end

      update_attributes(last_task_created_at: Time.now)

      tasks
    end

    def generate_task_for_assignable(a)
      data.symbolize_keys!

      reminding_at = default_deadline.ago(begin_to_remind * 60)

      Task.create! do |t|
        t.name = name
        t.data = data.select{ |k, v| !(k.to_s.start_with?('deadline_')) }
        t.task_type = plan_type
        t.deadline = calculate_deadline(plan_type, data)
        t.reminding_at = reminding_at
        t.status = :new
        t.finished_at = nil
        t.autocompletable = autocompletable
        t.create_assignable(
          assignee_id: a.assignee_id,
          assignee_type: a.assignee_type,
        )
        #t.callables = callables
        t.callables = []
        callables.each do |c|
          t.callables << Callable.create!(callback_id: c.callback_id, callback_type: c.callback_type)
        end
      end
    end

    def default_deadline
      now = Time.now
      case plan_type.to_sym
      when :daily then now.end_of_day
      when :weekly then now.end_of_week
      when :monthly then now.end_of_month
      when :quarterly then now.end_of_quarter
      when :yearly then now.end_of_year
      end
    end

    class << self
      # 返回所有需要创建新任务的计划
      #
      # 判断条件：
      # 1) `enabled_at`在当前时间之前；
      # 2) `last_task_created_at`在周期内。
      #   2.1) 日计划：`Time.now.beginning_of_day <= last_task_created_at <= Time.now.end_of_day`
      #   2.2) 周计划：`Time.now.beginning_of_week <= last_task_created_at <= Time.now.end_of_week`
      #   2.3) 月计划：`Time.now.beginning_of_month <= last_task_created_at <= Time.now.end_of_month`
      #   2.4) 季计划：`Time.now.beginning_of_quarter <= last_task_created_at <= Time.now.end_of_quarter`
      #   2.5) 年计划：`Time.now.beginning_of_year <= last_task_created_at <= Time.now.end_of_year`
      def active
        where{
          sift(:active_by_type, 'daily') |
          sift(:active_by_type, 'weekly') |
          sift(:active_by_type, 'monthly') |
          sift(:active_by_type, 'quarterly') |
          sift(:active_by_type, 'yearly')

        }
      end

      def active_by_type(type)
        now = Time.now
        beginning_at = case type.to_sym
                       when :daily then now.beginning_of_day
                       when :weekly then now.beginning_of_week
                       when :monthly then now.beginning_of_month
                       when :quarterly then now.beginning_of_quarter
                       when :yearly then now.beginning_of_year
                       end

        squeel{ (plan_type == type) &
                ((last_task_created_at <= beginning_at) | (last_task_created_at == nil)) &
                (enabled_at <= now) }
      end
    end
  end
end


module Scheherazade
  class CharacterBuilder < Struct.new(:character)
    AUTO = 'Auto'  # Note: must be equal? to AUTO (i.e. same object), not just ==

    def initialize(character)
      super
      @chain = [character]
      while (c = story.characters[@chain.first])
        @chain.unshift c
      end
      @model = Story.to_model(@chain.first)
      raise NameError, "Character not defined: #{@chain.first}" unless @model
      story.send :building, @ar = @model.new
      @chain.each{|c| story.current[c] = @ar }
    end

    # builds a character, filling required attributes,
    # default attributes for this type of character and
    # the attributes given.
    #
    # If the built object is invalid, the attributes with
    # errors will also be filled.
    #
    # An invalid object may be returned. In particular, it is
    # possible that it is only invalid because an associated
    # model is not yet valid.
    #
    def build(attribute_list = nil)
      @seq = (story.counter[@model] += 1)
      lists = [required_attributes, *default_attribute_lists, attribute_list]
      lists.prepend [:created_at] if @ar.has_attribute?(:created_at)
      attribute_list = lists.map{|al| canonical(al)}.inject(:merge)
      log(:building, attribute_list)
      set_attributes(attribute_list)
      unless @ar.valid?
        attribute_list = canonical(@ar.errors.map{|attr, _| attr})
        log(:fixing_errors, attribute_list)
        set_attributes(attribute_list)
      end
      yield @ar if block_given?
      log(:final_value, @ar)
      @ar
    end

  private
    def log(action, *args)
      Scheherazade.log(action, @model, *args)
    end

    def required_attributes
      @model.validators.select{|v| v.is_a?(ActiveModel::Validations::PresenceValidator) && v.options.empty?}
            .flat_map(&:attributes)
    end

    def default_attribute_lists
      story.fill_attributes.values_at(*@chain)
    end

    def set_attributes(attributes)
      attributes.each do |attr, value|
        set_attribute(attr, value)
      end
    end

    def set_attribute(attribute, value = AUTO)
      if assoc = @model.reflect_on_association(attribute)
        # It's possible that build records are not yet valid, so we
        # can expect errors on associations. At least for these reasons:
        return if @ar.send(attribute).present?

        value = assoc.klass.name.underscore.to_sym if value == AUTO
        value = value_for_association(assoc, value)
      elsif value.equal?(AUTO)
        value = automatic_value_for_basic_attribute(attribute)
      end
      log :setting, attribute, value
      @ar.send("#{attribute}=", value)
    end

    def value_for_association(assoc, associated_character)
      log(:setting_assocation, associated_character)
      key = assoc.options[:as] || assoc.active_record.name.underscore.to_sym
      opts = {key => @ar} if [:has_many, :has_one].include? assoc.macro
      ar =  if associated_character.is_a?(Symbol)
              story.current[associated_character] || self.class.new(associated_character).build(opts)
            else
              associated_character
            end
      case assoc.macro
      when :belongs_to
        ar
      when :has_and_belongs_to_many
        [ar]
      when :has_one
        ar
      when :has_many
        if ar.respond_to?(:persisted?) && ar.persisted?
          if ar.send(assoc.active_record.name.underscore) != @ar
            log :additional_character, assoc.name
            [self.class.new(associated_character).build(opts)]
          else
            [ar]
          end
        else
          [*ar]
        end
      end
    end

    def automatic_value_for_basic_attribute(attribute)
      seq_string = " {#{@seq}}" if @seq > 1
      case @model.columns_hash[attribute.to_s].try(:type)
      when :integer  then @seq
      when :float    then @seq
      when :decimal  then "#{@seq}.99"
      when :datetime then Time.now - 1.day + @seq
      when :date     then Date.today - 1.year + @seq
      when :string   then
        case attribute
        when :email
          "joe#{@seq}@example.com"
        when :name, :title
          "Example #{@model.name.humanize}#{seq_string}"
        else "Some #{attribute}#{seq_string}"
        end
      when :text     then "Some #{attribute} text#{seq_string}"
      when :boolean  then false
      else
        if enum = @model.enum_definitions[attribute]
          @model.send(attribute.to_s.pluralize).keys.first
        else
          puts "Unknown type for #{@model}##{attribute}"
          nil
        end
      end
    end

    # Converts an attribute_list to a single Hash;
    # some of the values may be set to AUTO.
    #
    #   canonical [:foo, {:bar => 42}]
    #    # => {:foo => AUTO, :bar => 42}
    #
    def canonical(attribute_list)
      case attribute_list
      when nil then {}
      when Hash then attribute_list
      when Array
        attribute_list.map do |attributes|
          case attributes
          when Symbol
            {attributes => AUTO}
          when Hash
            attributes
          else
            raise "Unexpected attributes #{attributes}"
          end
        end
        .inject({}, :merge)
      else
        raise "Unexpected attribute_list #{attribute_list}"
      end
    end

    def story
      Story.current
    end
  end
end


module Scheherazade
  class RedefinitionError < Exception
  end

  class Story < Hash
    attr_reader :fill_attributes, :characters, :counter, :current, :parent

    module ClassMethods
      def current
        (Thread.current[:scheherazade_stories] ||= []).last || TOP
      end

      # Begins a story within the current story.
      # Should be balanced with a call to +end+
      #
      def begin
        (Thread.current[:scheherazade_stories] ||= []).push Story.new
        current
      end

      # Ends the current substory and comes back
      # to the previous current story
      #
      def end(opts = nil)
        current.send :rollback, opts && opts[:rollback]
        Thread.current[:scheherazade_stories].pop
        current
      end

      # Begins a substory, yields, and ends the story
      #
      def tell(opts = nil)
        yield self.begin
      ensure
        self.end(opts)
      end

      def to_character(character_or_model)
        case character_or_model
        when Class
          character_or_model.name.underscore.to_sym
        when Symbol
          character_or_model
        else
          raise ArgumentError, "expected character or Model, got #{character_or_model.ancestors}"
        end
      end

      def to_character!(character_or_model)
        to_character(character_or_model) unless character_or_model.is_a?(Symbol)
      end

      # Returns a Model or nil
      def to_model(character)
        character.to_s.camelize.safe_constantize
      end
    end
    extend ClassMethods
    delegate :begin, :end, :tell, :to_character, :to_character!, :to_model, :to => 'self.class'

    def initialize(parent = self.class.current)
      super(){|h, k| parent[k] if parent }
      @borrowed = []
      @parent = parent
      @current = Hash.new do |h, k|
        if (char = to_character!(k))
          h[char]
        else
          h[k] = borrow(parent.current[k]) if parent
        end
      end

      @fill_attributes = Hash.new{|h, k| parent.fill_attributes[k] if parent }
      @characters = Hash.new {|h, k| parent.characters[k] if parent }
      @counter = parent ? parent.counter.dup : Hash.new(0)
      @filling = []
      @after_imagine = {}
      @built = []
    end

    TOP = new(nil)

    # Creates a character with the given attributes
    #
    # A character can be designated either by the model (e.g. `User`), the corresponding
    # symbol (`:user`) or the symbol for a specialized type of character, defined using +fill+
    # (e.g. `:admin`).
    #
    # The attributes can be nil, a list of symbols, a hash or a combination of both
    # These, along with attributes passed to +fill+ for the current stories
    # and the mandatory attributes for the model will be provided.
    #
    # If some fields generate validation errors, they will be provided also.
    #
    # For associations, the values can also be a character (Symbol or Model),
    # integers (meaning the default Model * that integer) or arrays of characters.
    #
    #    imagine(:user, :account, :company => :fortune_500_company, :emails => 3)
    #
    # Similarly:
    #
    #    User.imagine(...)
    #    :user.imagine(...)
    #
    # This record (and any other imagined through associations) will become the
    # current character in the current story:
    #
    #    story.current[User] # => nil
    #    story.tell do
    #      :admin.imagine # => A User record
    #      story.current[:admin] # => same
    #      story.current[User]   # => same
    #    end
    #    story.current[User] # => nil
    #
    def imagine(character_or_model, attributes = nil)
      character = to_character(character_or_model)
      prev, @building = @building, [] # because method might be re-entrant
      CharacterBuilder.new(character).build(attributes) do |ar|
        ar.save!
        # While errors on records associated with :has_many will prevent records
        # from being saved, they won't for :belongs_to, so:
        @building.each do |built|
          raise ActiveRecord::RecordInvalid, built unless built.persisted? || built.valid?
        end
        Scheherazade.log(:saving, character, ar)
        handle_callbacks(@building)
      end
    ensure
      @built.concat(@building)
      @building = prev
    end

    def get(character)
      current[character] || imagine(character)
    end

    # Allows one to temporarily override the current characters while
    # the given block executes
    #
    def with(temp_current)
      keys = temp_current.keys.map{|k| to_character(k)}
      previous_values = current.values_at(*keys)
      current.merge!(Hash[keys.zip(temp_current.values)])
      yield
    ensure
      current.merge!(Hash[keys.zip(previous_values)])
    end

    def fill(character_or_model, *with)
      char = to_character(character_or_model)
      raise RedefinitionError, "#{char} already defined for this story" if fill_attributes.has_key? char
      fill_attributes[char] = with
      @characters[char] = current_fill unless to_model(char)
      begin
        @filling.push(char)
        yield
      ensure
        @filling.pop
      end if block_given?
    end

    def after_imagine(&block)
      raise NotImplementedError unless model = to_model(current_fill)
      @after_imagine[model] = block
    end

    alias_method :==, :equal?
    alias_method :eql?, :equal?

    protected
    def current_fill
      @filling.last or raise "Expected to be inside a story.fill"
    end

    def handle_callbacks(on)
      @parent.handle_callbacks(on) if @parent
      on.each do |char|
        if (ai = @after_imagine[char.class])
          ai.yield(char)
        end
      end
    end

    def borrow(ar_instance)
      @borrowed << ar_instance if ar_instance
      ar_instance
    end

    def rollback(hard)
      @built.each(&:destroy) if hard
      @borrowed.each(&:reload)
    end

    def building(ar)
      @building << ar if @building # Condition needed in case we use CharacterBuilder outside of call to `imagine`
    end
  end
end


module Triumph
  class AchievementsController < ApplicationController
    # GET /achievements
    # GET /achievements.xml
    def index
      @achievements = Achievement.all

      respond_to do |format|
        format.html # index.html.erb
        format.xml  { render :xml => @achievements }
      end
    end

    # GET /achievements/1
    # GET /achievements/1.xml
    def show
      @achievement = Achievement.find(params[:id])
      @conditions = @achievement.achievement_conditions

      respond_to do |format|
        format.html # show.html.erb
        format.xml  { render :xml => @achievement }
      end
    end

    # GET /achievements/new
    # GET /achievements/new.xml
    def new
      @achievement = Achievement.new

      respond_to do |format|
        format.html # new.html.erb
        format.xml  { render :xml => @achievement }
      end
    end

    # GET /achievements/1/edit
    def edit
      @achievement = Achievement.find(params[:id])
    end

    # POST /achievements
    # POST /achievements.xml
    def create
      @achievement = Achievement.new(params[:triumph_achievement])
      @achievement.observe_class.singularize.downcase!

      if @achievement.save
        achievement_condition = AchievementCondition.new(params[:achievement_condition])
        achievement_condition.achievement_id = @achievement.id
        if achievement_condition.save
          redirect_to(@achievement, :notice => 'Achievement was successfully created.')
        else
          flash[:error] = "Failed to save achievement conditions"
        end
      else
        render :action => "new"
      end
    end

    # PUT /achievements/1
    # PUT /achievements/1.xml
    def update
      @achievement = Achievement.find(params[:id])
      @achievement.observe_class.downcase!

      respond_to do |format|
        if @achievement.update_attributes(params[:triumph_achievement])
          format.html { redirect_to(@achievement, :notice => 'Achievement was successfully updated.') }
          format.xml  { head :ok }
        else
          format.html { render :action => "edit" }
          format.xml  { render :xml => @achievement.errors, :status => :unprocessable_entity }
        end
      end
    end

    # DELETE /achievements/1
    # DELETE /achievements/1.xml
    def destroy
      @achievement = Achievement.find(params[:id])
      @achievement.destroy

      respond_to do |format|
        format.html { redirect_to(triumph_achievements_url) }
        format.xml  { head :ok }
      end
    end

  end
end


# -*- encoding: utf-8 -*-
module TaskManager
  class Task < ActiveRecord::Base
    extend Enumerize

    has_one :assignable, as: :target
    has_many :callables, as: :target

    enumerize :task_type, in: [:daily, :weekly, :monthly, :quarterly, :yearly]
    enumerize :status, in: [:new, :in_process, :expired, :finished]

    serialize :data, ActiveRecord::Coders::Hstore

    default_value_for :status, :new

    attr_accessible :data, :deadline, :name, :reminding_at, :status,
       :task_type, :finished_at, :autocompletable

    validates_presence_of :name, :task_type, :status, :assignable, :deadline

    def assignee
      assignable.assignee
    end

    def callbacks
      callables.collect(&:callback)
    end

    def remindable?
      return false if reminding_at.nil?

      (status == :new || status == :in_process) &&
        (deadline - reminding_at) / 60 >= 2 * 24 * 60 * 60
    end

    def next_reminding_at
      if remindable?
        seconds = (deadline - reminding_at) / 60

        reminding_at.since(seconds)
      end
    end

    # 提醒任务执行者，任务即将到期，并且修改任务下次提醒时间。
    # 如果`next_reminding_at`返回`nil`，则表示该任务不再需要提醒。
    def remind
      Task.transaction do
        assignee.remind_of_expiring_task(self) if assignee.respond_to?(:remind_of_expiring_task)

        update_attributes!(reminding_at: next_reminding_at)
      end
    end

    def expire
      Task.transaction do
        callbacks.each do |c|
          c.call(self) if c.respond_to?(:call)
        end

        update_attributes!(status: :expired)
      end
    end

    def finish
      update_attributes(finished_at: Time.now, status: :finished)
    end

    class << self
      def active
        where("status = ? OR status = ?", :new, :in_process)
      end

      def just_expired
        active.where("deadline <= ?", Time.now)
      end

      def remindable
        active.where("reminding_at <= ?", Time.now)
      end

      def autocompletable
        where(autocompletable: true)
      end
    end
  end
end


Dir.glob(File.join(File.dirname(__FILE__), *%w[primitives *.rb])) do |file|
  require file.sub(/\.rb$/, '')
end

module DeferrableGratification
  # Trivial operations which return Deferrables.
  #
  # Used internally by the library, and may be useful in cases where you need
  # to return a Deferrable to keep API compatibility but the result is already
  # available.
  #
  # {DeferrableGratification} extends this module, and thus the methods here
  # are accessible via the {DG} alias.
  module Primitives
    # Return a Deferrable which immediately succeeds, passing 0 or more values
    # to callbacks.
    def success(*values)
      blank.tap {|d| d.succeed(*values) }
    end

    # Return a Deferrable which immediately succeeds, passing a constant value
    # to callbacks.
    def const(value)
      success(value)
    end

    # Return a Deferrable which immediately fails with an exception.
    #
    # @overload failure(message)
    #   Passes +RuntimeError.new(message)+ to errbacks.
    # @overload failure(exception_class)
    #   Passes +exception_class.new+ to errbacks.
    # @overload failure(exception_class, message)
    #   Passes +exception_class.new(message)+ to errbacks.
    # @overload failure(exception)
    #   Passes +exception+ to errbacks.
    def failure(exception_class_or_message, message_or_nil = nil)
      blank.tap do |d|
        d.fail(
          case exception_class_or_message
          when Exception
            raise ArgumentError, "can't specify both exception and message" if message_or_nil
            exception_class_or_message
          when Class
            exception_class_or_message.new(message_or_nil)
          else
            RuntimeError.new(exception_class_or_message.to_s)
          end)
      end
    end

    # Return a completely uninteresting Deferrable.
    def blank
      DeferrableGratification::DefaultDeferrable.new
    end
  end
end


module FantasticRobot
  # This object represents a sendAudio request
  class Request::SendAudio < Request::Base

    MAX_FILE_SIZE = 50*1024*1024

    attr_accessor :chat_id, :audio, :duration, :performer, :title, :reply_to_message_id, :reply_markup

    validates :chat_id, :audio, presence: true
    validates :chat_id, numericality: true, unless: "chat_id.to_s[0,1] == '@'"
    validates :duration, :reply_to_message_id, numericality: true, allow_blank: true

    validate :file_length

    def initialize(attributes = {})
      super(attributes)
      @method = :sendAudio
    end

    private

    # Function to check that the file size isn't excesive.
    def file_length
      if self.audio.is_a?(File) && self.audio.size > MAX_FILE_SIZE
        self.errors.add :audio, "It's length is excesive. #{MAX_FILE_SIZE} is the limit."
        return false
      end

      return true
    end
  end
end


require "faraday"
require "open-uri"

module FantasticRobot
  class Connection
    attr_reader :conn

    def initialize
      # Create the connection object to make calls to the API
      @conn = Faraday.new(:url => "https://api.telegram.org/bot#{FantasticRobot.configuration.api_key}/") do |faraday|
        faraday.request :multipart
        faraday.request :url_encoded
        faraday.response :logger                  # log requests to STDOUT
        faraday.adapter  :net_http  # make requests with Net::HTTP
      end
    end

    # Function to call API passing a payload
    def api_call method, payload
      raise ArgumentError, "API method not specified." if method.blank?

      payload ||= {}

      res = @conn.post method.to_s, payload

      raise Faraday::Error, "Wrong response: #{res.inspect}" if (res.status != 200)

      return res
    end

    def get_by_path path
      return open("https://api.telegram.org/bot#{FantasticRobot.configuration.api_key}/#{path}")
      raise Faraday::Error, "Wrong response: #{res.inspect}" if (res.status != 200)
    end
  end
end


# encoding: utf-8

##
# Oedipus Sphinx 2 Search.
# Copyright © 2012 Chris Corbyn.
#
# See LICENSE file for details.
##

module Oedipus
  # Representation of a search index for querying.
  class Index
    attr_reader :name

    # Initialize the index named +name+ on the connection +conn+.
    #
    # @param [Symbol] name
    #   the name of an existing index in sphinx
    #
    # @param [Connection] conn
    #   an instance of Oedipus::Connection for querying
    def initialize(name, conn)
      @name    = name.to_sym
      @conn    = conn
      @builder = QueryBuilder.new(name)
    end

    # Insert the record with the ID +id+.
    #
    # @example
    #   index.insert(42, title: "example", views: 22)
    #
    # @param [Integer] id
    #   the unique ID of the document in the index
    #
    # @param [Hash] hash
    #   a symbol-keyed hash of data to insert
    #
    # @return [Fixnum]
    #   the number of rows inserted (currently always 1)
    def insert(id, hash)
      @conn.execute(*@builder.insert(id, hash))
    end

    # Update the record with the ID +id+.
    #
    # @example
    #   index.update(42, views: 25)
    #
    # @param [Integer] id
    #   the unique ID of the document in the index
    #
    # @param [Hash] hash
    #   a symbol-keyed hash of data to set
    #
    # @return [Fixnum]
    #   the number of rows updated (1 or 0)
    def update(id, hash)
      @conn.execute(*@builder.update(id, hash))
    end

    # Completely replace the record with the ID +id+.
    #
    # @example
    #   index.replace(42, title: "New title", views: 25)
    #
    # @param [Integer] id
    #   the unique ID of the document in the index
    #
    # @param [Hash] hash
    #   a symbol-keyed hash of data to insert
    #
    # @return [Fixnum]
    #   the number of rows inserted (currentl always 1)
    def replace(id, hash)
      @conn.execute(*@builder.replace(id, hash))
    end

    # Delete the record with the ID +id+.
    #
    # @example
    #   index.delete(42)
    #
    # @param [Integer] id
    #   the unique ID of the document in the index
    #
    # @return [Fixnum]
    #   the number of rows deleted (currently always 1 or 0)
    def delete(id)
      @conn.execute(*@builder.delete(id))
    end

    # Fetch a single document by its ID.
    #
    # Returns the Hash of attributes if found, otherwise nil.
    #
    # @param [Fixnum] id
    #   the ID of the document
    #
    # @return [Hash]
    #   the attributes of the record
    def fetch(id)
      search(id: id)[:records].first
    end

    # Perform a search on the index.
    #
    # Either one or two arguments may be passed, with either one being mutually
    # optional.
    #
    # @example Fulltext search
    #   index.search("cats AND dogs")
    #
    # @example Fulltext search with attribute filters
    #   index.search("cats AND dogs", author_id: 57)
    #
    # @example Attribute search only
    #   index.search(author_id: 57)
    #
    # When performing a faceted search, the base query is inherited by each facet, which
    # may override (or refine) the query.
    #
    # The results returned include a :facets key, containing the results for each facet.
    #
    # @example Performing a faceted search
    #   index.search(
    #     "cats | dogs",
    #     category_id: 7,
    #     facets: {
    #       popular: {views:        Oedipus.gt(150)},
    #       recent:  {published_at: Oedipus.gt(Time.now.to_i - 7 * 86400)}
    #     }
    #   )
    #
    # To perform an n-dimensional faceted search, add a :facets option to each
    # facet. Each facet will inherit from its immediate parent, which inerits
    # from its parent, up to the root query.
    #
    # @example Performing a n-dimensional faceted search
    #   index.search(
    #     "cats | dogs",
    #     facets: {
    #       popular: {
    #         views: Oedipus.gte(1000),
    #         facets: {
    #           in_title: "@title (%{query})"
    #         }
    #       }
    #     }
    #   )
    #
    # The results in a n-dimensional faceted search are returned with each set
    # of facet results in turn containing a :facets element.
    #
    # @param [String] query
    #   a fulltext query
    #
    # @param [Hash] options
    #   attribute filters, limits, sorting, facets and other options
    #
    # @option [Hash] facets
    #   variations on the main search to return nested in the result
    #
    # @option [Array] attrs
    #   attributes to fetch from the index, either as Symbols, or SphinxQL fragments
    #
    # @option [Hash] order
    #   an attr => direction mapping of sort orders
    #
    # @option [Fixnum] limit
    #   a limit to apply, defaults to 20 inside Sphinx itself
    #
    # @option [Fixnum] offset
    #   an offset to apply, defaults to 0
    #
    # @option [Object] everything_else
    #   all additional options are taken to be attribute filters
    #
    # @return [Hash]
    #   a Hash containing meta data, with the records in :records, and if any
    #   facets were included, the facets inside the :facets Hash
    def search(*args)
      expand_facet_tree(multi_search(deep_merge_facets(args)))
    end

    # Perform a faceted search on the index, using a base query and one or more facets.
    #
    # This method is deprecated and will be removed in version 1.0. Use #search instead.
    #
    # @deprecated
    #
    # @see #search
    def faceted_search(*args)
      search(*args)
    end

    # Perform a a batch search on the index.
    #
    # A Hash of queries is passed, whose keys are used to collate the results in
    # the return value.
    #
    # Each query may either by a string (fulltext search), a Hash (attribute search)
    # or an array containing both.  In other words, the same arguments accepted by
    # the #search method.
    #
    # @example
    #   index.multi_search(
    #     cat_results: ["cats", { author_id: 57 }],
    #     dog_results: ["dogs", { author_id: 57 }]
    #   )
    #
    # @param [Hash] queries
    #   a hash whose keys map to queries
    #
    # @return [Hash]
    #   a Hash whose keys map 1:1 with the input Hash, each element containing the
    #   same results as those returned by the #search method.
    def multi_search(queries)
      unless queries.kind_of?(Hash)
        raise ArgumentError, "Argument must be a Hash of named queries (#{queries.class} given)"
      end

      stmts       = []
      bind_values = []

      queries.each do |key, args|
        str, *values = @builder.select(*extract_query_data(args))
        stmts.push(str, "SHOW META")
        bind_values.push(*values)
      end

      rs = @conn.multi_query(stmts.join(";\n"), *bind_values)

      Hash[].tap do |result|
        queries.keys.each do |key|
          records, meta = rs.shift, rs.shift
          result[key] = meta_to_hash(meta).tap do |r|
            r[:records] = records.map { |hash|
              hash.inject({}) { |o, (k, v)| o.merge!(k.to_sym => v) }
            }
          end
        end
      end
    end

    private

    def meta_to_hash(meta)
      Hash[].tap do |hash|
        meta.each do |m|
          n, v = m.values
          case n
          when "total_found", "total" then hash[n.to_sym] = v.to_i
          when "time"                 then hash[:time] = v.to_f
          when /\Adocs\[\d+\]\Z/      then (hash[:docs] ||= []).tap { |a| a << v.to_i }
          when /\Ahits\[\d+\]\Z/      then (hash[:hits] ||= []).tap { |a| a << v.to_i }
          when /\Akeyword\[\d+\]\Z/   then (hash[:keywords] ||= []).tap { |a| a << v }
          else hash[n.to_sym] = v
          end
        end

        if hash.key?(:docs) && hash.key?(:hits) && hash.key?(:keywords)
          hash[:docs] = Hash[(hash[:keywords]).zip(hash[:docs])]
          hash[:hits] = Hash[(hash[:keywords]).zip(hash[:hits])]
        end
      end
    end

    def extract_query_data(args, default_query = "")
      args = [args] unless Array === args

      unless (1..2) === args.size
        raise ArgumentError, "Wrong number of query arguments (#{args.size} for 1..2)"
      end

      case args[0]
      when String then [args[0],       args.fetch(1, {}).dup]
      when Hash   then [default_query, args[0].dup          ]
      else raise ArgumentError, "Invalid query argument type #{args.first.class}"
      end
    end

    def expand_facet_tree(result)
      Hash[].tap do |tree|
        result.each do |k, v|
          t = tree

          k.each do |name|
            f = t[:facets] ||= {}
            t = f[name]    ||= {}
          end

          t.merge!(v)
        end
      end
    end

    def deep_merge_facets(base_args, list = {}, path = [])
      # FIXME: Try and make this shorter and more functional in style
      base_query, base_options = extract_query_data(base_args)

      facets = base_options.delete(:facets)

      list.merge!(path => [base_query, base_options])

      unless facets.nil?
        facets.each do |k, q|
          facet_query, facet_options = extract_query_data(q, base_query)

          deep_merge_facets(
            [
              facet_query.gsub("%{query}", base_query),
              base_options.merge(facet_options)
            ],
            list,
            path.dup << k
          )
        end
      end

      list
    end
  end
end


# already required in Google API
# require 'SecureRandom'

module GoogleDirectory

  # DirectoryService Ruby API Commands
  # https://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/AdminDirectoryV1/DirectoryService
  module UsersCommands

    # Usage hints
    # https://github.com/google/google-api-ruby-client/issues/360

    # get multiple users
    # if you don't want the defaults { max_results: 10, order_by: 'email' }
    # you must override (a nil disables the option)
    def users_list( attributes: {} )
      defaults = { max_results: 10, order_by: 'email' }
      filters  = defaults.merge( attributes )
      response = service.list_users( filters )
      {response: response, attributes: filters, command: :users_list}
    end

  end
end


module Whereabouts
  extend ActiveSupport::Concern

  module ClassMethods
    # Accepts a symbol that will define the inherited
    # type of Address.  Defaults to the parent class.
    def has_whereabouts klass=:address, *args
      options = args.extract_options!
      # extend Address with class name if not defined.
      unless klass == :address || Object.const_defined?(klass.to_s.camelize)
        create_address_class(klass.to_s.camelize)
      end

      # Set the has_one relationship and accepts_nested_attributes_for.
      has_one klass, :as => :addressable, :dependent => :destroy
      accepts_nested_attributes_for klass

      # Define a singleton on the class that returns an array
      # that includes the address fields to validate presence of
      # or an empty array
      if options[:validate] && options[:validate].is_a?(Array)
        validators = options[:validate]
        set_validators(klass, validators)
      else
        validators = []
      end
      define_singleton_method validate_singleton_for(klass) do validators end

      # Check for geocode in options and confirm geocoder is defined.
      # Also defines a singleton to return a boolean about geocoding.
      if options[:geocode] && options[:geocode] == true && defined?(Geocoder)
        geocode = true
        set_geocoding(klass)
      else
        geocode = false
      end
      define_singleton_method geocode_singleton_for(klass) do geocode end
    end

    private
    # Accepts a symbol defining the class and
    # returns a symbol to define a singleton
    # on the class like :address_whereabouts_geocode?
    def geocode_singleton_for klass
      "#{klass.to_s}_whereabouts_geocode?".to_sym
    end

    # Accepts a symbol defining the class and
    # returns a symbol to define a singleton
    # on the class like :address_whereabouts_validate_fields
    def validate_singleton_for klass
      "#{klass.to_s}_whereabouts_validate_fields".to_sym
    end

    # Sets validates_presence_of fields for the Address based on the
    # singleton method created on the Address addressable_type class.
    def set_validators klass, fields=[]
      _single = validate_singleton_for(klass)
      klass.to_s.camelize.constantize.class_eval do
        fields.each do |f|
          validates_presence_of f,
            :if => lambda {|a| a.addressable_type.constantize.send(_single).include?(f)}
        end
      end
    end

    # Geocode the address using Address#geocode_address if the
    # geocode_singleton is true and the record is either new or
    # has been updated.
    def set_geocoding klass
      _single = geocode_singleton_for(klass)
      klass.to_s.camelize.constantize.class_eval do
        geocoded_by :geocode_address
        after_validation :geocode,
          :if => lambda {|a| a.addressable_type.constantize.send(_single) && (a.new_record? || a.changed?)}
      end
    end

    # Generate a new class using Address as the superclass.
    # Accepts a string defining the inherited type.
    def create_address_class(class_name, &block)
      klass = Class.new Address, &block
      Object.const_set class_name, klass
    end
  end
end
# Include the module into ActiveRecord::Base
class ActiveRecord::Base
  include Whereabouts
end


module Qwirk

  # Base Worker Class for any class that will be processing messages from topics or queues
  # By default, it will consume messages from a queue with the class name minus the Worker postfix.
  # For example, the queue call is unnecessary as it will default to a value of 'Foo' anyways:
  #  class FooWorker
  #    include Qwirk::Worker
  #    queue 'Foo'
  #    def perform(obj)
  #      # Perform work on obj
  #    end
  #  end
  #
  # A topic can also be specified.  Note that for JMS, this is only supported under ActiveMQ.  On others,
  # each thread for a given worker will act as a separate subscriber.
  # (For ActiveMQ - see http://activemq.apache.org/virtual-destinations.html):
  #  class FooWorker
  #    include Qwirk::Worker
  #    topic 'Zulu'
  #    def perform(obj)
  #      # Perform work on obj
  #    end
  #  end
  #
  # TODO (maybe):
  # Filters can also be specified within the class:
  #  class FooWorker
  #    include Qwirk::Worker
  #    filter 'age > 30'
  #    def perform(obj)
  #      # Perform work on obj
  #    end
  #  end
  #
  #
  module Worker
    include Qwirk::BaseWorker

    attr_accessor :message
    attr_reader   :thread, :status, :impl, :start_worker_time, :start_read_time, :start_processing_time

    module ClassMethods
      def queue(name, opts={})
        # If we're using the default name but we still want to set queue options, then a name won't be given.
        if name.kind_of?(Hash)
          @queue_options = name
        else
          @queue_name = name.to_s
          @queue_options = opts
        end
      end

      def topic(name, options={})
        @topic_name = name.to_s
        @queue_options = options
      end

      # Set the fail_queue
      # target =>
      #   boolean
      #     true - exceptions in the worker will cause the message to be forwarded to the queue of <default-name>Fail
      #       For instance, an Exception in FooWorker#perform will forward the message to the queue FooFail
      #     false - exceptions will not result in the message being forwarded to a fail queue
      #   string - equivalent to true but the string defines the name of the fail queue
      def fail_queue(target, opts={})
        @fail_queue_target = target
      end

      def fail_queue_target
        @fail_queue_target
      end

      # Defines the default value of the fail_queue_target.  For extenders of this class, the default will be true
      # but extenders can change this (ReplyWorker returns exceptions to the caller so it defaults to false).
      def default_fail_queue_target
        true
      end

      def queue_name(default_name)
        return @queue_name if @queue_name
        return nil if @topic_name
        return default_name
      end

      def topic_name
        @topic_name
      end

      def queue_options
        @queue_options ||= {}
      end

      def fail_queue_name(worker_config)
        # TBD - Set up fail_queue as a config
        target = self.class.fail_queue_target
        # Don't overwrite if the user set to false, only if it was never set
        target = self.class.default_fail_queue_target if target.nil?
        if target == true
          return Qwirk.fail_queue_name(config.name)
        elsif target == false
          return nil
        elsif target.kind_of?(String)
          return target
        else
          raise "Invalid fail queue: #{target}"
        end
      end
    end

    def self.included(base)
      Qwirk::BaseWorker.included(base)
      base.extend(ClassMethods)
    end

    def init(index, worker_config)
      @status           = 'Started'
      @stopped          = false
      @processing_mutex = Mutex.new
      self.index        = index
      self.config       = worker_config
      @impl             = worker_config.create_worker
    end

    def start
      @thread = Thread.new do
        java.lang.Thread.current_thread.name = "Qwirk worker: #{self}" if RUBY_PLATFORM == 'jruby'
        #Qwirk.logger.debug "#{worker}: Started thread with priority #{Thread.current.priority}"
        event_loop
      end
    end

    # Workers will be starting and stopping on an as needed basis.  Thus, when they get a stop command they should
    # clean up any resources.  We don't want to clobber resources while a message is being processed so processing_mutex will surround
    # message processessing and worker closing.
    # From a JMS perspective, stop all workers (close consumer and session), stop the config.
    # From an InMemory perspective, we don't want the workers stopping until all messages in the queue have been processed.
    # Therefore we want to stop the
    def stop
      Qwirk.logger.debug "#{self}: In worker stop"
      @status  = 'Stopping'
      @stopped = true
      @processing_mutex.synchronize do
        # This should interrupt @impl.receive_message above and cause it to return nil
        @impl.stop
      end
    end

    def join(timeout=nil)
      time_str = timeout ? "#{timeout} seconds" : 'indefinitely'
      Qwirk.logger.info "#{self}: Waiting #{time_str} for worker to stop"
      # See http://jira.codehaus.org/browse/JRUBY-6896
      if timeout && !self.thread.join(timeout)
        Qwirk.logger.warn "#{self}: WARNING - worker stop commanded but thread did not exit gracefully in #{timeout} seconds"
      else
        self.thread.join
      end
      Qwirk.logger.info "#{self}: worker stop complete"
    end

    def perform(object)
      raise "#{self}: Need to override perform method in #{self.class.name} in order to act on #{object}"
    end

    def to_s
      "#{config.name}:#{index}"
    end

    # Allow override of backtrace logging in case the client doesn't want to get spammed with it (maybe just config instead?)
    def log_backtrace(e)
      Qwirk.logger.error "\t#{e.backtrace.join("\n\t")}"
    end

    # Start the event loop for handling messages off the queue
    def event_loop
      Qwirk.logger.debug "#{self}: Starting receive loop"
      @start_worker_time = Time.now
      until @stopped || (config.stopped? && @impl.ready_to_stop?)
        Qwirk.logger.debug "#{self}: Waiting for read"
        @start_read_time = Time.now
        msg = @impl.receive_message
        if msg
          @start_processing_time = Time.now
          Qwirk.logger.debug {"#{self}: Done waiting for read in #{@start_processing_time - @start_read_time} seconds"}
          delta = config.timer.measure do
            @processing_mutex.synchronize do
              on_message(msg)
              @impl.acknowledge_message(msg)
            end
          end
          Qwirk.logger.info {"#{self}::on_message (#{'%.1f' % delta}ms)"} if self.config.log_times
          Qwirk.logger.flush if Qwirk.logger.respond_to?(:flush)
        end
      end
      Qwirk.logger.info "#{self}: Exiting"
    rescue Exception => e
      @status = "Terminated: #{e.message}"
      Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
    ensure
      @status = 'Stopped'
      Qwirk.logger.flush if Qwirk.logger.respond_to?(:flush)
      config.worker_stopped(self)
    end

    def on_message(message)
      # TBD - Is it necessary to provide underlying message to worker?  Should we generically provide access to message attributes?  Do filters somehow fit in here?
      @message = message
      object = @impl.message_to_object(message)
      Qwirk.logger.debug {"#{self}: Received Object: #{object}"}
      perform(object)
    rescue Exception => e
      on_exception(e)
    ensure
      Qwirk.logger.debug {"#{self}: Finished processing message"}
    end

    def on_exception(e)
      Qwirk.logger.error "#{self}: Messaging Exception: #{e.message}"
      log_backtrace(e)
      @impl.handle_failure(message, e, @fail_queue_name) if @fail_queue_name
    rescue Exception => e
      Qwirk.logger.error "#{self}: Exception in exception reply: #{e.message}"
      log_backtrace(e)
    end

    def fail_queue_name
      @fail_queue_name
    end
  end
end


require 'active_record/connection_adapters/postgresql_adapter'
# Extends AR to add Hstore functionality.
module ActiveRecord

  # Adds methods for deleting keys in your hstore columns
  class Base

    # Deletes all keys from a specific column in a model. E.g.
    #   Person.delete_key(:info, :father)
    # The SQL generated will be:
    #   UPDATE "people" SET "info" = delete("info",'father');
    def self.delete_key attribute, key
      raise "invalid attribute #{attribute}" unless column_names.include?(attribute.to_s)
      update_all([%(#{attribute} = delete("#{attribute}",?)),key])
    end

    # Deletes many keys from a specific column in a model. E.g.
    #   Person.delete_key(:info, :father, :mother)
    # The SQL generated will be:
    #   UPDATE "people" SET "info" = delete(delete("info",'father'),'mother');
    def self.delete_keys attribute, *keys
      raise "invalid attribute #{attribute}" unless column_names.include?(attribute.to_s)
      delete_str = "delete(#{attribute},?)"
      (keys.count-1).times{ delete_str = "delete(#{delete_str},?)" }
      update_all(["#{attribute} = #{delete_str}", *keys])
    end

    # Deletes a key in a record. E.g.
    #   witt = Person.find_by_name("Ludwig Wittgenstein")
    #   witt.destroy_key(:info, :father)
    # It does not save the record, so you'll have to do it.
    def destroy_key attribute, key
      raise "invalid attribute #{attribute}" unless self.class.column_names.include?(attribute.to_s)
      new_value = send(attribute)
      new_value.delete(key.to_s)
      send("#{attribute}=", new_value)
      self
    end

    # Deletes a key in a record. E.g.
    #   witt = Person.find_by_name("Ludwig Wittgenstein")
    #   witt.destroy_key(:info, :father)
    # It does save the record.
    def destroy_key! attribute, key
      destroy_key(attribute, key).save
    end

    # Deletes many keys in a record. E.g.
    #   witt = Person.find_by_name("Ludwig Wittgenstein")
    #   witt.destroy_keys(:info, :father, :mother)
    # It does not save the record, so you'll have to do it.
    def destroy_keys attribute, *keys
      for key in keys
        new_value = send(attribute)
        new_value.delete(key.to_s)
        send("#{attribute}=", new_value)
      end
      self
    end

    # Deletes many keys in a record. E.g.
    #   witt = Person.find_by_name("Ludwig Wittgenstein")
    #   witt.destroy_keys!(:info, :father, :mother)
    # It does save the record.
    def destroy_keys! attribute, *keys
      raise "invalid attribute #{attribute}" unless self.class.column_names.include?(attribute.to_s)
      destroy_keys(attribute, *keys).save
    end

    # This method is replaced for Rails 3 compatibility.
    # All I do is add the condition when the field is a hash that converts the value
    # to hstore format.
    # IMHO this should be delegated to the column, so it won't be necessary to rewrite all
    # this method.
    def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
      attrs = {}
      attribute_names.each do |name|
        if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
          if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
            value = read_attribute(name)
            if self.class.columns_hash[name].type == :hstore && value && value.is_a?(Hash)
              value = value.to_hstore # Done!
            elsif value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time) || value.is_a?(Hash) || value.is_a?(Array))
              value = value.to_yaml
            end
            attrs[self.class.arel_table[name]] = value
          end
        end
      end
      attrs
    end
  end
  # This erro class is used when the user passes a wrong value to a hstore column.
  # Hstore columns accepts hashes or hstore valid strings. It is validated with
  # String#valid_hstore? method.
  class HstoreTypeMismatch < ActiveRecord::ActiveRecordError
  end

  module ConnectionAdapters

    class TableDefinition

      # Adds hstore type for migrations. So you can add columns to a table like:
      #   create_table :people do |t|
      #     ...
      #     t.hstore :info
      #     ...
      #   end
      def hstore(*args)
        options = args.extract_options!
        column_names = args
        column_names.each { |name| column(name, 'hstore', options) }
      end

    end

    PostgreSQLColumn.class_eval do
      # Does the type casting from hstore columns using String#from_hstore or Hash#from_hstore.
      def type_cast_code_with_hstore(var_name)
        type == :hstore ? "#{var_name}.from_hstore" : type_cast_code_without_hstore(var_name)
      end

      # Adds the hstore type for the column.
      def simplified_type_with_hstore(field_type)
        field_type == 'hstore' ? :hstore : simplified_type_without_hstore(field_type)
      end
      alias_method_chain :type_cast_code, :hstore
      alias_method_chain :simplified_type, :hstore
    end

    PostgreSQLAdapter.class_eval do

      def native_database_types_with_hstore
        native_database_types_without_hstore.merge({:hstore => { :name => "hstore" }})
      end

      # Quotes correctly a hstore column value.
      def quote_with_hstore(value, column = nil)
        if value && column && column.sql_type == 'hstore'
          raise HstoreTypeMismatch, "#{column.name} must have a Hash or a valid hstore value (#{value})" unless value.kind_of?(Hash) || value.valid_hstore?
          return quote_without_hstore(value.to_hstore, column)
        end
        quote_without_hstore(value,column)
      end

      alias_method_chain :quote, :hstore
      alias_method_chain :native_database_types, :hstore
    end
  end
end


module MyScripts
  # This script wraps up all work done on project in current directory,
  # adds all new(unversioned) files to git, commits all work done so far,
  # optionally bumps project version and pushes changes to remote repo
  #
  class Gitto < Script
    VERSION = '0.1.0'

    COMMAND_PATTERN = /^(\d+\.\d+\.\d+    # either explicit version (1.2.3)
                        (?:\.(.*?))?      # possibly followed by .build
                        |\.(.*?)          # or, just .build
                        |\d{1}0{0,2})$/x  # or, one digit followed by 0-2 zeroes (100/10/1 - bump major/minor/patch)

    def run
      usage "[0.1.2 - version, 100/10/1 - bump major/minor/patch, .build - add build] Commit message goes here" if @argv.empty?

      # First Arg may indicate version command if it matches pattern
      version_command = @argv[0] =~ COMMAND_PATTERN ? @argv.shift : nil

      # All the other args lumped into message, or default message
      if @argv.empty?
        commit_message = "Commit #{Time.now.to_s[0..-6]}"
        history_message = nil
      else
        commit_message = history_message = @argv.join(' ')
      end

      # Updating version only if version command set
      if version_command
        puts "Updating version with #{version_command}"
        if history_message
          system %Q{rake "version[#{version_command},#{history_message}]"}
        else
          system %Q{rake version[#{version_command}]}
        end
      end

      puts "Adding all the changes"
      system "git add --all"

      puts "Committing everything with message: #{commit_message}"
      system %Q[git commit -a -m "#{commit_message}" --author arvicco]

      current_branch = `git branch`.strip
      puts "Pushing to (default) remote for branch: #{current_branch}"
      system "git push"
    end
  end
end


# product.rb
# Copyright (C) 2010-2012 Red Hat, Inc.
#
# Authors:
#   Akira TAGOH  <tagoh@redhat.com>
#
# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'bugzilla/api_template'

module Bugzilla
  # rdoc
  #
  # === Bugzilla::Product
  #
  # Bugzilla::Product class is to access
  # the Bugzilla::WebService::Product API that allows you to
  # list the available Products and get information about them.
  #

  class Product < APITemplate
    # rdoc
    #
    # ==== Bugzilla::Product#selectable_products
    #
    # Returns Hash table for the products information that the user
    # can search on. the Hash key is the product name and containing
    # a Hash table which contains id, name, description,
    # is_active, default_milestone, has_uncomfirmed, classification,
    # components, versions and milestones. please see
    # http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html#get
    # for more details.
    #

    def selectable_products
      ids = get_selectable_products
      Hash[*get(ids)['products'].map { |x| [x['name'], x] }.flatten]
    end # def selectable_products

    # rdoc
    #
    # ==== Bugzilla::Product#enterable_products
    #
    # Returns Hash table for the products information that the user
    # can enter bugs against. the Hash key is the product name and
    # containing a Hash table which contains id, name, description,
    # is_active, default_milestone, has_uncomfirmed, classification,
    # components, versions and milestones. please see
    # http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html#get
    # for more details.
    #

    def enterable_products
      ids = get_enterable_products
      Hash[*get(ids)['products'].map { |x| [x['name'], x] }.flatten]
    end # def enterable_products

    # rdoc
    #
    # ==== Bugzilla::Product#accessible_products
    #
    # Returns Hash table for the products information that the user
    # can search or enter bugs against. the Hash key is the product
    # name and containing a Hash table which contains id, name, description,
    # is_active, default_milestone, has_uncomfirmed, classification,
    # components, versions and milestones. please see
    # http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html#get
    # for more details.
    #

    def accessible_products
      ids = get_accessible_products
      Hash[*get(ids)['products'].map { |x| [x['name'], x] }.flatten]
    end # def accessible_products

    # rdoc
    #
    # ==== Bugzilla::Product#get_selectable_products
    #
    # Raw Bugzilla API to obtain the products that the user can
    # search on.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
    #

    # rdoc
    #
    # ==== Bugzilla::Product#get_enterable_products
    #
    # Raw Bugzilla API to obtain the products that the user can
    # enter bugs against.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
    #

    # rdoc
    #
    # ==== Bugzilla::Product#get_accessible_products
    #
    # Raw Bugzilla API to obtain the products that the user can
    # search or enter bugs against.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
    #

    # rdoc
    #
    # ==== Bugzilla::Product#get(params)
    #
    # Raw Bugzilla API to obtain a list of information about the products
    # passed to it.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Product.html
    #

    protected

    def _get_selectable_products(cmd, *_args)
      @iface.call(cmd)
    end # def _get_selectable_products

    def _get_enterable_products(cmd, *_args)
      @iface.call(cmd)
    end # def _get_entrable_products

    def _get_accessible_products(cmd, *_args)
      @iface.call(cmd)
    end # def _get_accessible_products

    def _get(cmd, ids, *_args)
      # This is still in experimental and apparently the behavior was changed since 4.2.
      # We don't keep the backward-compatibility and just require the proper version here.
      requires_version(cmd, 4.2)

      params = {}

      if ids.is_a?(Hash)
        raise ArgumentError, format('Invalid parameter: %s', ids.inspect) unless ids.include?('ids') || ids.include?('names')
        params[:ids] = ids['ids'] || ids['names']
      elsif ids.is_a?(Array)
        r = ids.map { |x| x.is_a?(Integer) ? x : nil }.compact
        if r.length != ids.length
          params[:names] = ids
        else
          params[:ids] = ids
        end
      else
        if ids.is_a?(Integer)
          params[:ids] = [ids]
        else
          params[:names] = [ids]
        end
      end

      @iface.call(cmd, params)
    end # def _get

    def __create(cmd, *args)
      # FIXME
    end # def _create

    def __update(cmd, *_args)
      requires_version(cmd, 4.4)

      # FIXME
    end # def _update
  end # class Product
end # module Bugzilla


# bugzilla.rb
# Copyright (C) 2010-2014 Red Hat, Inc.
#
# Authors:
#   Akira TAGOH  <tagoh@redhat.com>
#
# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'bugzilla/skeleton'

module Bugzilla
  # rdoc
  #
  # === Bugzilla::Bugzilla
  #
  # Bugzilla::Bugzilla class is to access the
  # Bugzilla::WebService::Bugzilla API that provides functions
  # tell you about Bugzilla in general.
  #

  class Bugzilla < Skeleton
    # rdoc
    #
    # ==== Bugzilla::Bugzilla#check_version(version_)
    #
    # Returns Array contains the result of the version check and
    # Bugzilla version that is running on.
    #

    def check_version(version_)
      v = version
      f = false
      if v.is_a?(Hash) && v.include?('version') &&
         Gem::Version.new(v['version']) >= Gem::Version.new(version_.to_s)
        f = true
      end

      [f, v['version']]
    end # def check_version

    # rdoc
    #
    # ==== Bugzilla::Bugzilla#requires_version(cmd, version_)
    #
    # Raise an exception if the Bugzilla doesn't satisfy
    # the requirement of the _version_.
    #

    def requires_version(cmd, version_)
      v = check_version(version_)
      raise NoMethodError, format('%s is not supported in Bugzilla %s', cmd, v[1]) unless v[0]
    end # def requires_version

    # rdoc
    #
    # ==== Bugzilla::Bugzilla#version
    #
    # Raw Bugzilla API to obtain the Bugzilla version.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
    #

    # rdoc
    #
    # ==== Bugzilla::Bugzilla#extensions
    #
    # Raw Bugzilla API to obtain the information about
    # the extensions that are currently installed and enabled in
    # the Bugzilla.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
    #

    # rdoc
    #
    # ==== Bugzilla::Bugzilla#timezone
    #
    # Raw Bugzilla API to obtain the timezone that Bugzilla
    # expects dates and times in.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
    #

    # rdoc
    #
    # ==== Bugzilla::Bugzilla#time
    #
    # Raw Bugzilla API to obtain the information about what time
    # the bugzilla server thinks it is, and what timezone it's
    # running on.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
    #

    # rdoc
    #
    # ==== Bugzilla::Bugzilla#parameters
    #
    # Raw Bugzilla API to obtain parameter values currently used in Bugzilla.
    #
    # See http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bugzilla.html
    #

    protected

    def _version(cmd, *_args)
      @iface.call(cmd)
    end # def _version

    def _extensions(cmd, *_args)
      requires_version(cmd, 3.2)

      @iface.call(cmd)
    end # def _extensions

    def _timezone(cmd, *_args)
      @iface.call(cmd)
    end # def _timezone

    def _time(cmd, *_args)
      requires_version(cmd, 3.4)

      @iface.call(cmd)
    end # def _time

    def _parameters(cmd, *_args)
      requires_version(cmd, 4.4)

      @iface.call(cmd)
    end # def _parameters

    def __last_audit_time(cmd, *_args)
      requires_version(cmd, 4.4)

      # FIXME
    end # def _last_audit_time
  end # class Bugzilla
end # module Bugzilla


module Clacks
  class Service
    # TODO: find the root cause and remove workaround below, @see http://stackoverflow.com/a/16007377/3774613
    begin require 'active_support'; rescue Exception => e; end

    require 'mail'

    # In practice timeouts occur when there is no activity keeping an IMAP connection open.
    # Timeouts occuring are:
    #   IMAP server timeout: typically after 30 minutes with no activity.
    #   NAT Gateway timeout: typically after 15 minutes with an idle connection.
    # The solution to this is for the IMAP client to issue a NOOP (No Operation) command
    # at intervals, typically every 29 minutes. We choose default 10 minutes.
    WATCHDOG_SLEEP = ENV['CLACKS_WATCHDOG_SLEEP'] || 10 * 60   # default 10 minutes

    def run
      begin
        Clacks.logger.info "Clacks v#{Clacks::VERSION} started"
        if Clacks.config[:pop3]
          run_pop3
        elsif Clacks.config[:imap]
          run_imap
        else
          raise "Either a POP3 or an IMAP server must be configured"
        end
      rescue Exception => e
        fatal(e)
      end
    end

    def stop
      $STOPPING = true
      exit unless finding?
    end

    private

    def fatal(e)
      unless e.is_a?(SystemExit) || e.is_a?(SignalException)
        Clacks.logger.fatal("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}")
      end
      stop
      raise e
    end

    def run_pop3
      config = Clacks.config[:pop3]
      Clacks.logger.info("Clacks POP3 polling #{config[:user_name]}@#{config[:address]}")
      # TODO: if $DEBUG
      processor = Mail::IMAP.new(config)
      poll(processor)
    end

    def run_imap
      config = Clacks.config[:imap]
      options = Clacks.config[:find_options]
      processor = Mail::IMAP.new(config)
      if $DEBUG
        Net::IMAP.debug = true
        Clacks.logger.level = Logger::DEBUG
      end
      imap_validate_options(options)
      if imap_idle_support?(processor)
        Clacks.logger.info("Clacks IMAP idling #{config[:user_name]}@#{config[:address]}")
        imap_idling(processor)
      else
        Clacks.logger.info("Clacks IMAP polling #{config[:user_name]}@#{config[:address]}")
        poll(processor)
      end
    end

    # Follows mostly the defaults from the Mail gem
    def imap_validate_options(options)
      options ||= {}
      options[:mailbox] ||= 'INBOX'
      options[:count]   ||= 5
      options[:order]   ||= :asc
      options[:what]    ||= :first
      options[:keys]    ||= 'ALL'
      options[:delete_after_find] ||= false
      options[:mailbox] = Net::IMAP.encode_utf7(options[:mailbox])
      if options[:archivebox]
        options[:archivebox] = Net::IMAP.encode_utf7(options[:archivebox])
      end
      options
    end

    def imap_idle_support?(processor)
      processor.connection { |imap| imap.capability.include?("IDLE") }
    end

    def imap_idling(processor)
      imap_watchdog
      loop do
        begin
          processor.connection do |imap|
            @imap = imap
            # select the mailbox to process
            imap.select(Clacks.config[:find_options][:mailbox])
            loop {
              break if stopping?
              finding { imap_find(imap) }
              # http://tools.ietf.org/rfc/rfc2177.txt
              Clacks.logger.debug('imap.idle start')
              imap.idle do |r|
                Clacks.logger.debug('imap.idle yields')
                if r.instance_of?(Net::IMAP::UntaggedResponse) && r.name == 'EXISTS'
                  imap.idle_done unless r.data == 0
                elsif r.instance_of?(Net::IMAP::ContinuationRequest)
                  Clacks.logger.info(r.data.text)
                end
              end
            }
          end
        rescue Net::IMAP::BadResponseError => e
          unless e.message == 'Could not parse command'
            Clacks.logger.error("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}")
          end
          # reconnect in next loop
        rescue Net::IMAP::Error, IOError => e
          # OK: reconnect in next loop
        rescue Errno::ECONNRESET => e
          # Connection reset by peer: reconnect in next loop
        rescue StandardError => e
          Clacks.logger.error("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}")
          sleep(5) unless stopping?
        end
        break if stopping?
      end
    end

    # http://tools.ietf.org/rfc/rfc2177.txt
    def imap_watchdog
      Thread.new do
        loop do
          begin
            Clacks.logger.debug('watchdog sleeps')
            sleep(WATCHDOG_SLEEP)
            Clacks.logger.debug('watchdog woke up')
            @imap.idle_done
            Clacks.logger.debug('watchdog signalled idle process')
          rescue StandardError => e
            Clacks.logger.debug { "watchdog received error: #{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}" }
            # noop
          rescue Exception => e
            fatal(e)
          end
        end
      end
    end

    # Keep processing emails until nothing is found anymore,
    # or until a QUIT signal is received to stop the process.
    def imap_find(imap)
      options = Clacks.config[:find_options]
      delete_after_find = options[:delete_after_find]
      begin
        break if stopping?
        uids = imap.uid_search(options[:keys] || 'ALL')
        uids.reverse! if options[:what].to_sym == :last
        uids = uids.first(options[:count]) if options[:count].is_a?(Integer)
        uids.reverse! if (options[:what].to_sym == :last && options[:order].to_sym == :asc) ||
                         (options[:what].to_sym != :last && options[:order].to_sym == :desc)
        processed = 0
        expunge = false
        uids.each do |uid|
          break if stopping?
          source = imap.uid_fetch(uid, ['RFC822']).first.attr['RFC822']
          mail = nil
          begin
            mail = Mail.new(source)
            mail.mark_for_delete = true if delete_after_find
            Clacks.config[:on_mail].call(mail)
          rescue StandardError => e
            Clacks.logger.error(e.message)
            Clacks.logger.error(e.backtrace)
          end
          begin
            imap.uid_copy(uid, options[:archivebox]) if options[:archivebox]
            if delete_after_find && (mail.nil? || mail.is_marked_for_delete?)
              expunge = true
              imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
            end
          rescue StandardError => e
            Clacks.logger.error(e.message)
          end
          processed += 1
        end
        imap.expunge if expunge
      end while uids.any? && processed == uids.length
    end

    def poll(processor)
      polling_msg = if polling?
        "Clacks polling every #{poll_interval} seconds."
      else
        "Clacks polling for messages once."
      end
      Clacks.logger.info(polling_msg)

      find_options = Clacks.config[:find_options]
      on_mail = Clacks.config[:on_mail]
      loop do
        break if stopping?
        finding {
          processor.find(find_options) do |mail|
            if stopping?
              mail.skip_deletion
            else
              begin
                on_mail.call(mail)
              rescue StandardError => e
                Clacks.logger.error(e.message)
                Clacks.logger.error(e.backtrace)
              end
            end
          end
        }
        break if stopping? || !polling?
        sleep(poll_interval)
      end
    end

    def poll_interval
      Clacks.config[:poll_interval]
    end

    def polling?
      poll_interval > 0
    end

    def finding(&block)
      @finding = true
      yield
    ensure
      @finding = false
    end

    def finding?
      @finding
    end

    def stopping?
      $STOPPING
    end

    at_exit {
      Clacks.logger.info("Clacks stopped.") if $STOPPING
    }

  end
end


module Mead
  class Ead
    # factor out :baseurl, :file and :url into an options object?
    attr_accessor :containers, :ead, :baseurl, :file, :url, :doc, :eadid, :series_present,
      :c01s_series_answer

    # options include :file and :base_url
    def initialize(opts={})
      @eadid = opts[:eadid] || nil
      @file    = opts[:file] || nil
      @baseurl = opts[:baseurl] || nil
      @url     = opts[:url] || nil
      @containers = []

      get_ead
      find_eadid unless @eadid
      crawl_for_containers
    end

    def get_ead
      if @eadid.nil? and @url.nil? and @file.nil? and @baseurl
        raise 'Cannot get EAD based on params.'
      end
      if @file and @file.is_a? File
        @file.rewind if @file.eof?
        @ead = @file.read
      elsif @url
        @ead = open(@url).read
      elsif @baseurl
        @ead = open(File.join(@baseurl, @eadid + '.xml')).read
      end
      @doc = Nokogiri::XML(@ead)
    end

    def find_eadid
      begin
        @eadid = @doc.xpath('//xmlns:eadid').first.text
      rescue => e
        raise 'Need an eadid and none has been given and it cannot be found in the EAD XML.'
      end
    end

    def crawl_for_containers
      c01s.each_with_index do |c, i|
        dids = c.xpath('.//xmlns:container').map{|c| c.parent}.uniq
        #c.xpath('xmlns:c02/xmlns:did').map do |did|
        dids.map do |did|
          info = {}
          if c01s_series?
            info[:series] = i + 1 # if all the c01s are at the file level this fails
          else
            info[:series] = 0
          end
          info[:mead] = create_mead(did, i)
          info[:title] = concat_title(did)
          # FIXME
          info[:containers] = text_containers(did)
          @containers << info
        end
      end
    end

    def text_containers(did)
      did.xpath('xmlns:container').map do |container|
        text = ''
        text << container.attribute('localtype').text + ' ' if container.attribute('localtype')
        text << container.text if container.text
        text
      end
    end

    def c01s
      @doc.xpath('//xmlns:dsc/xmlns:c01')
    end

    def c01s_series?
      @c01s_series_answer ||= c01s.length == series_c01s.length
    end

    def series_c01s
      @doc.xpath("//xmlns:dsc/xmlns:c01[@level='series']")
    end

    def create_mead(did, i)
      mead = [@eadid.dup]
      if c01s_series?
        mead << "%03d" % (i + 1) #series
      else
        mead << '001'
      end
      begin
        mead << specific_containers(did)
      rescue
        return @mead = mead.flatten.join('-')
      end
      mead << '001' # stub for first record
      @mead = mead.flatten.join('-')
    end

    def concat_title(did)
      title = ''
      title << did.xpath('xmlns:unittitle').text if did.xpath('xmlns:unittitle')
      if did.xpath('xmlns:unittitle') and did.xpath('xmlns:unitdate') and !did.xpath('xmlns:unitdate').text.empty?
        title << ', ' << did.xpath('xmlns:unitdate').text
      end
      if did.xpath('xmlns:unitid') and !did.xpath('xmlns:unitid').text.empty?
        title << ' (' + did.xpath('xmlns:unitid').text + ')'
      end
      title
    end

    def specific_containers(did)
      containers = did.xpath('xmlns:container')
      container_values = []
      if containers.length == 1
        container_values << make_box(did.xpath('xmlns:container')[0])
        container_values << '000'
      elsif containers.length == 2
        container_values << make_box(did.xpath('xmlns:container')[0])
        container_values << make_box(did.xpath('xmlns:container')[1],3)
      elsif containers.length > 2
        raise "I can't create a mead identifier with more than 2 containers in a did!"
      else
        raise "Do we really have zero containers?!"
      end
      return container_values
    end

    def make_box(container, padding=4)
      # FIXME: pad based on first part of range for folder +++
      padder = "%0" + padding.to_s + 's'
      text = (padder % container.text).gsub(' ','0').gsub('.','_').gsub('-', '~')
      container_type(container) + text
    end

    def container_type(container)
      match =''
      CONTAINER_MAPPING.each do |k,v|
        if container.attribute('localtype').text == v or
            container.attribute('localtype').text.downcase == v
          match = k
        end
      end
      match
    end

    def to_csv
      Mead::Ead.to_csv(self.containers)
    end

    def self.to_csv(container_list)
      if CSV.const_defined? :Reader
        csv_class = FasterCSV # old CSV was loaded
      else
        csv_class = CSV # use CSV from 1.9
      end
      csv_string = csv_class.generate do |csv|
        # FIXME
        csv << ['mead','title','series', 'containers']
        #csv << ['mead','title','series']
        container_list.each do |container|
          csv << [container[:mead], container[:title], container[:series], container[:containers].join(', ')]
          #csv << [container[:mead], container[:title], container[:series]]
        end
      end
    end

    def valid?
      if unique_meads.length == @containers.length
        if short_meads?
          false
        else
          true
        end
      else
        false
      end
    end

    def unique_meads
      @containers.collect{|container| container[:mead]}.uniq
    end

    def long_meads
      unique_meads.select{|m| m.split('-').length > 2}
    end

    def short_meads
      unique_meads.select{|m| m.split('-').length <= 2}
    end

    def short_meads?
      if unique_meads.length == long_meads.length
        false
      else
        true
      end
    end

    def invalid
      duplicates = dups
      @containers.select{|container| duplicates.include?(container[:mead])}
    end

    def dups
      meads.inject({}) {|h,v| h[v]=h[v].to_i+1; h}.reject{|k,v| v==1}.keys.sort
    end

    def meads
      @containers.collect{|container| container[:mead]}
    end

  end
end


module OscMacheteRails
  # Methods that deal with pbs batch job status management
  # within a Rails ActiveRecord model
  module Statusable

    delegate :not_submitted?, :submitted?, :completed?, :passed?, :failed?, :active?, to: :status

    def status=(s)
      super(OSC::Machete::Status.new(s).char)
    end

    # getter returns a Status value from CHAR or a Status value
    def status
      OSC::Machete::Status.new(super)
    end

    # delete the batch job and update status
    # may raise PBS::Error as it is unhandled here!
    def stop(update: true)
      return unless status.active?

      job.delete
      update(status: OSC::Machete::Status.failed) if update
    end

    def self.included(obj)
      # track the classes that include this module
      self.classes << Kernel.const_get(obj.name) unless obj.name.nil?

      # add class methods
      obj.send(:extend, ClassMethods)

      begin
        obj.class_eval do
          # Store job object info in a JSON column and replace old column methods
          if respond_to?(:table_exists?) && table_exists? && respond_to?(:column_names) && column_names.include?('job_cache')
            store :job_cache, accessors: [ :script, :pbsid, :host ], coder: JSON
            delegate :script_name, to: :job
            define_method :job_path do
              job.path
            end
          else
            define_method(:job_cache) do
              {
                script: (job_path && script_name) ? Pathname.new(job_path).join(script_name) : nil,
                pbsid: pbsid,
                host: nil
              }
            end
          end
        end
      rescue StandardError => e
        msg = "\nIf you are precompiling assets, you can ignore this:\n\tError thrown: #{e.inspect}"
        msg += "\n\tError thrown in OscMacheteRails::Statusable.included when trying to add job_cache to the class including Statusable: #{obj.name}."
        msg += "\n\tjob_cache method was not added to class." unless obj.method_defined? :job_cache
        msg += "\n\n"
        STDERR.puts msg
      end
    end

    def self.classes
      @classes ||= []
    end

    # for each Statusable, call update_status! on active jobs
    def self.update_status_of_all_active_jobs
      # to eager load classes, set config.eager_load to true or execute Rails.application.eager_load!
      if self.classes.empty?
        Rails.logger.warn "Statusable.classes Array is empty. This should contain a list of all the classes that include Statusable. " \
          "This could occur if the Rails app is not configured to eager load classes."
      else
        Rails.logger.debug "Updating active job statuses via Statusable.update_status_of_all_active_jobs."
      end

      self.classes.each do |klass|
        klass.active.to_a.each(&:update_status!) if klass.respond_to?(:active)
      end
    end

    # class methods to extend a model with
    module ClassMethods
      # scope to get all of the jobs that are in an active state
      # or have a pbsid
      def active
        # FIXME: what about OR i.e. where
        #
        #     status in active_values OR (pbsid != null and status == null)
        #
        # will need to use STRING for the sql instead of this.
        where(status: OSC::Machete::Status.active_values.map(&:char))
      end
    end

    # Setter that accepts an OSC::Machete::Job instance
    #
    # @param [Job] new_job The Job object to be assigned to the Statusable instance.
    def job=(new_job)
      if self.has_attribute?(:job_cache)
        self.script = new_job.script_path.to_s
        self.pbsid = new_job.pbsid
        self.host = new_job.host if new_job.respond_to?(:host)
      else
        self.script_name = new_job.script_name
        self.job_path = new_job.path.to_s
        self.pbsid = new_job.pbsid
      end

      begin
        self.status = new_job.status
      rescue PBS::Error => e
        # a safe default
        self.status = OSC::Machete::Status.queued

        # log the error
        Rails.logger.error("After submitting the job with pbsid: #{pbsid}," \
                           " checking the status raised a PBS::Error: #{e.message}")
      end
    end

    # Returns associated OSC::Machete::Job instance
    def job
      OSC::Machete::Job.new(job_cache.symbolize_keys)
    end

    # Build the results validation method name from script_name attr
    # using ActiveSupport methods
    #
    # Call this using the Rails console to see what method you should implement
    # to support results validation for that job.
    #
    # @return [String] A string representing a validation method name from script_name attr
    # using ActiveSupport methods
    def results_validation_method_name
      File.basename(script_name, ".*").underscore.parameterize('_') + "_results_valid?"
    end

    # A hook that can be overidden with custom code
    # also looks for default validation methods for existing
    # WARNING: THIS USES ActiveSupport::Inflector methods underscore and parameterize
    #
    # @return [Boolean] true if the results script is present
    def results_valid?
      valid = true

      if self.respond_to?(:script_name) && !script_name.nil?
        if self.respond_to?(results_validation_method_name)
          valid = self.send(results_validation_method_name)
        end
      end

      valid
    end

    #FIXME: should have a unit test for this!
    # job.update_status! will update and save object
    # if submitted? and ! completed? and status changed from previous state
    # force will cause status to update regardless of completion status,
    # redoing the validations. This way, if you are fixing validation methods
    # you can use the Rails console to update the status of a Workflow by doing this:
    #
    #     Container.last.jobs.each {|j| j.update_status!(force: true) }
    #
    # Or for a single statusable such as job:
    #
    #     job.update_status!(force: true)
    #
    # FIXME: should log whether a validation method was called or
    # throw a warning that no validation method was found (the one that would have been called)
    #
    # @param [Boolean, nil] force Force the update. (Default: false)
    def update_status!(force: false)
      # this will make it easier to differentiate from current_status
      cached_status = status

      # by default only update if its an active job
      if  (cached_status.not_submitted? && pbsid) || cached_status.active? || force
        # get the current status from the system
        current_status = job.status

        # if job is done, lets re-validate
        if current_status.completed?
          current_status = results_valid? ? OSC::Machete::Status.passed : OSC::Machete::Status.failed
        end

        if (current_status != cached_status) || force
          self.status = current_status
          self.save
        end
      end

    rescue PBS::Error => e
      # we log the error but we just don't update the status
      Rails.logger.error("During update_status! call on job with pbsid #{pbsid} and id #{id}" \
                          " a PBS::Error was thrown: #{e.message}")
    end
  end
end


module MyScripts
  # Base class for all scripts. Subclass it and override run method with actual
  # work your script will be doing
  class Script
    def initialize( name, cli, argv, argf )
      @name = name
      @cli = cli
      @argv = argv
      @argf = argf
    end

    def version
      if self.class.const_defined? :VERSION
        self.class::VERSION  # Script's own version
      else
        VERSION # Gem version
      end
    end

    # This is where all action happens. Should be defined by all Script implementations
    def run
    end

    def system( *args)
      @cli.kernel.system *args
    end

    def puts( *args )
      @cli.stdout.puts *args
      nil
    end

    def gets
      @cli.stdin.gets
    end

    # Outputs usage notes (and optional extended explanation), then exits with code 1
    def usage( examples, explanation = nil )
      puts "Script #{@name} #{version} - Usage:"
      (examples.respond_to?(:split) ? examples.split("\n") : examples).map {|line| puts "    #{@name} #{line}"}
      puts explanation if explanation
      exit 1
    end

    # Outputs error text, then exits with code 1
    def error( text )
      puts "Script #{@name} #{version} - Error: #{text}"
      exit 1
    end

    def to_s
      "#{@name} #{@argv.join(' ')} -> #{self.class}"
    end
  end
end