#--
# Copyright (c) 2005-2013, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++


require 'ruote/exp/fe_concurrence'
require 'ruote/exp/iterator'


module Ruote::Exp

  #
  # This expression is a cross between 'concurrence' and 'iterator'.
  #
  # Please look at the documentation of 'iterator' to learn more about the
  # common options between 'iterator' and 'concurrent-iterator'.
  #
  #   pdef = Ruote.process_definition :name => 'test' do
  #     concurrent_iterator :on_val => 'alice, bob, charly', :to_var => 'v' do
  #       participant '${v:v}'
  #     end
  #   end
  #
  # will be equivalent to
  #
  #   pdef = Ruote.process_definition :name => 'test' do
  #     concurrence do
  #       participant 'alice'
  #       participant 'bob'
  #       participant 'charly'
  #     end
  #   end
  #
  # The 'on' and the 'to' attributes follow exactly the ones for the iterator
  # expression.
  #
  # When there is no 'to', the current iterated value is placed in the variable
  # named 'i'.
  #
  # The variable named 'ii' contains the current iterated index (an int bigger
  # or egal to 0).
  #
  # 'concurrent_iterator' does not understand commands like
  # rewind/break/jump/... like 'iterator' does, since it fires all its
  # branches when applied.
  #
  # == :on and arrays
  #
  # Given a workitem field named 'x' containing the array value
  # [ 'a', 'b', 'c' ] and a workitem field 'y' containing the string 'a, b, c',
  # this:
  #
  #   concurrent_iterator :on_field => 'x', :to_f => 'xx' do
  #     # ...
  #   end
  #
  # is equivalent to
  #
  #   concurrent_iterator :on => '$f:x', :to_f => 'xx' do
  #     # ...
  #   end
  #
  # is equivalent to
  #
  #   concurrent_iterator :on => '${f:y}', :to_f => 'xx' do
  #     # ...
  #   end
  #
  # == :to_field and :to_f, :to_var and :to_v, :to
  #
  # Those 4 lines are equivalent:
  #
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to_field => 'a' do ...
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to_f => 'a' do ...
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to => 'f:a' do ...
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to => 'a' do ...
  #
  # Those 3 lines are equivalent:
  #
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to_var => 'a' do ...
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to_v => 'a' do ...
  #   concurrent_iterator :on => [ 'ceo', 'cto' ], :to => 'v:a' do ...
  #
  # == :times and :branches
  #
  # Similarly to the iterator expression, the :times or the :branches attribute
  # may be used in stead of the 'on' attribute.
  #
  #   pdef = Ruote.process_definition :name => 'test' do
  #     concurrent_iterator :times => 3 do
  #       participant 'user${v:i}'
  #     end
  #   end
  #
  # is equivalent to
  #
  #   pdef = Ruote.process_definition :name => 'test' do
  #     concurrence do
  #       participant 'user0'
  #       participant 'user1'
  #       participant 'user2'
  #     end
  #   end
  #
  #
  # == ruote 2.3.0 and the citerator children
  #
  # Prior to ruote 2.3.0, the concurrent-iterator only considered one child
  # expression:
  #
  #   concurrent_iterator :times => 3 do
  #     participant 'al'
  #     participant 'bob' # 'bob' would never be reached
  #   end
  #
  # So one had to write:
  #
  #   concurrent_iterator :times => 3 do
  #     sequence do
  #       participant 'al'
  #       participant 'bob' # 'bob' would never be reached
  #     end
  #   end
  #
  # Ruote 2.3.0 lifts that restriction.
  #
  #
  # == options
  #
  # the concurrent_iterator accepts the same options for merging as its bigger
  # brother, the concurrence expression.
  #
  # :count, :merge (override, mix, isolate, stack, union, concat, deep, ignore),
  # :remaining (cancel, forget) and :over.
  #
  #
  # == add branches
  #
  # The 'add_branches'/'add_branch' expression can be used to add branches
  # to a concurrent-iterator while it is running.
  #
  #   concurrent_iterator :on => 'a, b, c' do
  #     sequence do
  #       participant :ref => 'worker_${v:i}'
  #       add_branches 'd, e', :if => '${v:/not_sufficient}'
  #     end
  #   end
  #
  # In this example, if the process level variable 'not_sufficient' is set to
  # true, workers d and e will be added to the iterated elements.
  #
  # Read more at the 'add_branches' expression description.
  #
  #
  # == 'citerator'
  #
  # 'citerator' is an alias for 'concurrent_iterator'.
  #
  #   pdef = Ruote.process_definition :name => 'test' do
  #     citerator :on_val => 'alice, bob, charly', :to_var => 'v' do
  #       participant '${v:v}'
  #     end
  #   end
  #
  class ConcurrentIteratorExpression < ConcurrenceExpression

    include IteratorMixin

    names :concurrent_iterator, :citerator

    ADD_BRANCHES_FIELD = '__add_branches__'

    # Overrides FlowExpression#register_child to make sure that persist is
    # not called.
    #
    def register_child(fei)

      h.children << fei
    end

    def add_branches(list)

      if h.times_iterator && list.size == 1

        count = (list.first.to_i rescue nil)

        list = (h.list_size + 0...h.list_size + count) if count
      end

      list.each do |val|

        h.list_size += 1

        workitem = Ruote.fulldup(h.applied_workitem)

        variables = { 'ii' => h.list_size - 1 }

        if h.to_v
          variables[h.to_v] = val
        else #if to_f
          workitem['fields'][h.to_f] = val
        end

        expid, subtree = if tree_children.size > 1
          [ h.fei['expid'], [ 'sequence', {}, tree_children ] ]
        else
          [ "#{h.fei['expid']}_0", tree_children[0] ]
        end

        launch_sub(
          expid,
          subtree,
          :workitem => workitem,
          :variables => variables,
          :child_id => h.list_size - 1)
      end
    end

    def reply(workitem)

      if ab = workitem['fields'].delete(ADD_BRANCHES_FIELD)

        add_branches(ab)

        if
          h.fei['wfid'] != workitem['fei']['wfid'] ||
          ( ! workitem['fei']['expid'].match(/^#{h.fei['expid']}_\d+$/))
        then
          do_persist
          return
        end
      end

      super(workitem)
    end

    protected

    def apply_children

      return reply_to_parent(h.applied_workitem) unless tree_children[0]

      list = determine_list

      return reply_to_parent(h.applied_workitem) if list.empty?

      h.to_v, h.to_f = determine_tos
      h.to_v = 'i' unless h.to_v or h.to_f

      h.list_size = 0

      add_branches(list)

      persist_or_raise
    end

    # Overrides the implementation found in ConcurrenceExpression
    #
    def count_list_size

      h.list_size
    end

    # TODO
    #
    def workitem_index(workitem)

      if %w[ highest lowest ].include?(h.cmerge)
        nil
      elsif m = workitem['fei']['subid'].match(/k(\d)+$/)
        m[1].to_i
      else
        nil
      end
    end
  end
end

