###
Transform an expression using a pattern. The
pattern can come from the integrals table or
the user-defined patterns.

The expression and free variable are on the stack.

The argument s is a null terminated list of transform rules.

For example, see the itab (integrals table)

Internally, the following symbols are used:

  F  input expression

  X  free variable, i.e. F of X

  A  template expression

  B  result expression

  C  list of conditional expressions

Puts the final expression on top of stack
(whether it's transformed or not) and returns
true is successful, false if not.


###



# p1 and p2 are tmps

#define F p3
#define X p4
#define A p5
#define B p6
#define C p7

transform = (s, generalTransform) ->
  transform_h = 0

  save()

  p1 = null

  p4 = pop() # X i.e. free variable
  p3 = pop() # F i.e. input expression

  if DEBUG
    console.log "         !!!!!!!!!   transform on: " + p3


  saveMetaBindings()

  set_binding(symbol(METAX), p4)

  # put constants in F(X) on the stack

  transform_h = tos
  push_integer(1)
  push(p3)
  push(p4)
  polyform(); # collect coefficients of x, x^2, etc.
  push(p4)
  
  bookmarkTosToPrintDecomps = tos - 2
  decomp(generalTransform)
  numberOfDecomps = tos - bookmarkTosToPrintDecomps

  if DEBUG
    console.log "  " + numberOfDecomps + " decomposed elements ====== "
    for i in [0...numberOfDecomps]
      console.log "  decomposition element " + i + ": " + stack[tos-1-i]

  transformationSuccessful = false

  if generalTransform
    # "general tranform" mode is supposed to be more generic than
    # "integrals" mode.
    # In general transform mode we get only one transformation
    # in s

    # simple numbers can end up matching complicated templates,
    # which we don't want.
    # for example "1" ends up matching "inner(transpose(a_),a_)"
    # since "1" is decomposed to "1" and replacing "a_" with "1"
    # there is a match.
    # Although this match is OK at some fundamental level, we want to
    # avoid it because that's not what the spirit of this match
    # is: "1" does not have any structural resemblance with
    # "inner(transpose(a_),a_)". There are probably better ways
    # to so this, for example we might notice that "inner" is an
    # anchor since it "sits above" any meta variables, so we
    # might want to mandate it to be matched at the top
    # of the tree. For the time
    # being let's just skip matching on simple numbers.
    if !isNumericAtom(p3)

      theTransform = s
      if DEBUG then console.log "applying transform: " + theTransform
      if DEBUG then console.log "scanning table entry " + theTransform

      push theTransform

      # replacements of meta variables. Note that we don't
      # use scan_meta because the pattern is not a string
      # that we have to parse, it's a tree already.
      # replace a_ with METAA in the passed transformation
      push symbol(SYMBOL_A_UNDERSCORE)
      push symbol(METAA)
      subst()

      # replace b_ with METAB in the passed transformation
      push symbol(SYMBOL_B_UNDERSCORE)
      push symbol(METAB)
      subst()

      # replace x_ with METAX in the passed transformation
      push symbol(SYMBOL_X_UNDERSCORE)
      push symbol(METAX)
      subst()

      p1 = pop()

      p5 = car(p1)
      if DEBUG then console.log "template expression: " + p5
      p6 = cadr(p1)
      p7 = cddr(p1)

      ###
      p5 = p1.tensor.elem[0]
      p6 = p1.tensor.elem[1]
      for i in [2..(p1.tensor.elem.length-1)]
        push p1.tensor.elem[i]
      list(p1.tensor.elem.length - 2)
      p7 = pop()
      ###



      if (f_equals_a(transform_h, generalTransform))
        # successful transformation,
        # transformed result is in p6
        transformationSuccessful = true
      else
        # the match failed but perhaps we can match
        # something lower down in the tree, so
        # let's recurse the tree

        if DEBUG then console.log "p3 at this point: " + p3

        transformedTerms = []

        if DEBUG then console.log "car(p3): " + car(p3)
        restTerm = p3

        if iscons(restTerm)
          transformedTerms.push car(p3)
          restTerm = cdr(p3)

        while (iscons(restTerm))
          secondTerm = car(restTerm)
          restTerm = cdr(restTerm)

          if DEBUG then console.log "tos before recursive transform: " + tos
          
          push(secondTerm)
          push_symbol(NIL)
          if DEBUG then console.log "testing: " + secondTerm
          #if (secondTerm+"") == "eig(A x,transpose(A x))()"
          #  debugger
          if DEBUG then console.log "about to try to simplify other term: " + secondTerm
          success = transform(s, generalTransform)          
          transformationSuccessful = transformationSuccessful or success

          transformedTerms.push pop()

          if DEBUG then console.log "tried to simplify other term: " + secondTerm + " ...successful?: " + success + " ...transformed: " + transformedTerms[transformedTerms.length-1]


        # recreate the tree we were passed,
        # but with all the terms being transformed
        if transformedTerms.length != 0
          for i in transformedTerms
            push i
          list(transformedTerms.length)
          p6 = pop()



  else # "integrals" mode
    for eachTransformEntry in s
      if DEBUG
        console.log "scanning table entry " + eachTransformEntry
        if (eachTransformEntry + "").indexOf("f(sqrt(a+b*x),2/3*1/b*sqrt((a+b*x)^3))") != -1
          debugger
      if eachTransformEntry
        scan_meta(eachTransformEntry)
        p1 = pop()

        p5 = cadr(p1)
        p6 = caddr(p1)
        p7 = cdddr(p1)

        ###
        p5 = p1.tensor.elem[0]
        p6 = p1.tensor.elem[1]
        for i in [2..(p1.tensor.elem.length-1)]
          push p1.tensor.elem[i]
        list(p1.tensor.elem.length - 2)
        p7 = pop()
        ###


        if (f_equals_a(transform_h, generalTransform))
          # there is a successful transformation,
          # transformed result is in p6
          transformationSuccessful = true
          break




  moveTos transform_h


  if transformationSuccessful
    #console.log "transformation successful"
    # a transformation was successful
    push(p6)
    Eval()
    p1 = pop()
    #console.log "...into: " + p1
    transformationSuccessful = true
  else
    # transformations failed
    if generalTransform
      # result = original expression
      p1 = p3
    else
      p1 = symbol(NIL)

  restoreMetaBindings()

  push(p1)

  restore()
  return transformationSuccessful

saveMetaBindings = ->
  push(get_binding(symbol(METAA)))
  push(get_binding(symbol(METAB)))
  push(get_binding(symbol(METAX)))


restoreMetaBindings = ->
  set_binding(symbol(METAX), pop())
  set_binding(symbol(METAB), pop())
  set_binding(symbol(METAA), pop())

# search for a METAA and METAB such that F = A

f_equals_a = (h, generalTransform) ->
  fea_i = 0
  fea_j = 0
  for fea_i in [h...tos]

    set_binding(symbol(METAA), stack[fea_i])
    if DEBUG
      console.log "  binding METAA to " + get_binding(symbol(METAA))
    for fea_j in [h...tos]

      set_binding(symbol(METAB), stack[fea_j])
      if DEBUG
        console.log "  binding METAB to " + get_binding(symbol(METAB))

      # now test all the conditions (it's an and between them)
      p1 = p7
      while (iscons(p1))
        push(car(p1))
        Eval()
        p2 = pop()
        if (isZeroAtomOrTensor(p2))
          break
        p1 = cdr(p1)

      if (iscons(p1))
        # conditions are not met,
        # skip to the next binding of metas
        continue
      push(p3);      # F = A?
      if DEBUG
        console.log "about to evaluate template expression: " + p5 +
          " binding METAA to " + get_binding(symbol(METAA)) +
          " and binding METAB to " + get_binding(symbol(METAB)) +
          " and binding METAX to " + get_binding(symbol(METAX))
      push(p5)
      if generalTransform
        originalexpanding = expanding
        expanding = false
      Eval()
      if generalTransform
        expanding = originalexpanding
      if DEBUG
        console.log "  comparing " + stack[tos-1] + " to: " + stack[tos-2]
      subtract()
      p1 = pop()
      if (isZeroAtomOrTensor(p1))
        if DEBUG
          console.log "binding METAA to " + get_binding(symbol(METAA))
          console.log "binding METAB to " + get_binding(symbol(METAB))
          console.log "binding METAX to " + get_binding(symbol(METAX))
          console.log "comparing " + p3 + " to: " + p5
        return 1;    # yes
  return 0;          # no
