#--
# 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.
#++


module Ruote::Exp

  #
  # 're-opening' the FlowExpression class to add the methods about variables.
  #
  class FlowExpression

    # A shortcut to the variables held in the expression (nil if none held).
    #
    def variables

      @h['variables']
    end

    # Returns a fresh hash of all the variables visible from this expression.
    #
    # This is used mainly when forgetting an expression.
    #
    def compile_variables

      vars = h.parent_id ? parent.compile_variables : {}
      vars.merge!(h.variables) if h.variables

      vars
    end

    # Looks up the value of a variable in expression tree
    # (seen from a leaf, it looks more like a stack than a tree)
    #
    def lookup_variable(var, prefix=nil)

      var, prefix = split_prefix(var, prefix)

      if prefix == '//'
        return @context.storage.get_engine_variable(var)
      end

      if prefix == '/' && par = parent
        return par.lookup_variable(var, prefix)
      end

      if h.variables and Ruote.has_key?(h.variables, var)
        return Ruote.lookup(h.variables, var)
      end

      if h.parent_id && h.parent_id['engine_id'] == @context.engine_id
        #
        # do not lookup variables in a remote engine ...

        (return parent.lookup_variable(var, prefix)) rescue nil
          # if the lookup fails (parent gone) then rescue and let go
      end

      k = var.split('.').first
      vars = { k => @context.storage.get_engine_variable(k) }
        #
        # engine var lookup might be expensive, only lookup the var we need

      Ruote.lookup(vars, var)
    end

    # A shortcut for #lookup_variable
    #
    alias :v :lookup_variable

    # A shortcut for #lookup_variable
    #
    alias :lv :lookup_variable

    # Sets a variable to a given value.
    # (will set at the appropriate level).
    #
    def set_variable(var, val, override=false)

      fexp, v = locate_set_var(var, override) || locate_var(var)

      fexp.un_set_variable(:set, v, val, (fexp.h.fei != h.fei)) if fexp
    end

    # Unbinds a variables.
    #
    def unset_variable(var, override=false)

      fexp, v = locate_set_var(var, override) || locate_var(var)

      fexp.un_set_variable(:unset, v, nil, (fexp.h.fei != h.fei)) if fexp
    end

    # TODO : rdoc rewrite needed
    #
    # This method is mostly used by the worker when looking up
    # a process name or participant name bound under a variable.
    #
    def iterative_var_lookup(k)

      v = lookup_variable(k)

      return [ k, v ] unless (v.is_a?(String) or v.is_a?(Symbol))

      iterative_var_lookup(v)
    end

    protected

    # Sets (or unsets) the value of a local variable
    #
    # val should be nil in case of 'unset'.
    #
    def un_set_variable(op, var, val, should_persist)

      result = if op == :set
        Ruote.set(h.variables, var, val)
      else # op == :unset
        Ruote.unset(h.variables, var)
      end

      if should_persist && r = try_persist # persist failed, have to retry

        self.h = r
        un_set_variable(op, var, val, true)

      else # success (even when should_persist == false)

        @context.storage.put_msg("variable_#{op}", 'var' => var, 'fei' => h.fei)
      end

      result
    end

    VAR_PREFIX_REGEX = /^(\/{0,2})\/*(.+)$/

    # Used by lookup_variable and set_variable to extract the
    # prefix in a variable name
    #
    def split_prefix(var, prefix)

      if prefix.nil?
        m = VAR_PREFIX_REGEX.match(var.to_s)
        prefix = m[1]
        var = m[2]
      end

      [ var, prefix ]
    end

    # Returns the flow expression that owns a variable (or the one
    # that should own it) and the var without its potential / prefixes.
    #
    # In other words:
    #
    #   [ owner, varname_without_slashes ]
    #
    # When a location for the variable could not be found, it returns:
    #
    #   [ nil, nil ]
    #
    def locate_var(var, prefix=nil)

      var, prefix = split_prefix(var, prefix)

      if prefix == '//' # engine variable
        nil
      elsif prefix == '/' && par = parent # process variable
        par.locate_var(var, prefix)
      elsif h.variables # it's here
        [ self, var ]
      elsif par = parent # look in the parent expression
        par.locate_var(var, prefix)
      else # uprooted var lookup...
        [ nil, nil ]
      end
    end

    # When used with override = true(ish), will try to locate the binding site
    # for the variable and return it.
    #
    # If override is set to 'sub', will stop before digging into the parent
    # subprocess.
    #
    def locate_set_var(var, override)

      hk = h.variables && h.variables.has_key?(var)

      if ( ! override) || var.match(/^\//)
        false
      elsif override == 'sub' && DefineExpression.is_definition?(tree) && ! hk
        false
      elsif hk
        [ self, var ]
      elsif par = parent
        par.locate_set_var(var, override)
      else
        false
      end
    end

    def set_v(key, value, opts={})

      if opts[:unset]
        unset_variable(key, opts[:override])
      else
        set_variable(key, value, opts[:override])
      end
    end

    def set_f(key, value, opts={})

      workitem = opts[:workitem] || h.applied_workitem

      if opts[:unset]
        Ruote.unset(workitem['fields'], key)
      else
        Ruote.set(workitem['fields'], key, value)
      end
    end

    PREFIX_REGEX = /^([^:]+):(.+)$/
    F_PREFIX_REGEX = /^f/

    def set_vf(key, value, opts={})

      field, key =
        if m = PREFIX_REGEX.match(key)
          [ F_PREFIX_REGEX.match(m[1]), m[2] ]
        else
          [ true, key ]
        end

      if field
        set_f(key, value, opts)
      else
        set_v(key, value, opts)
      end
    end
  end
end

