module Goose

  class QGenerator

    attr_reader :vari, :varj, :varf
    
    Zero_re = /\A [\+-]* (?: 0 | 0.0* ) \z/xms

    def initialize(gfor_handler, gdefun_handlers, precompiled_funcs)
      @gfor_handler    = gfor_handler
      @gdefun_handlers = gdefun_handlers
      @precompiled_funcs = precompiled_funcs
      @vari = []
      @varj = []
      @varf = []
    end

    def generate
      buf = ''
      buf << generate_var_decl
      buf << generate_func_def
      buf << generate_body
    end
    
    private ####################################################################################

    def exp2qexp(exp)
      arrays    = exp.recursive_select(:array_exp)
      pderefs   = exp.recursive_select(:pderef_exp)
      qexp = exp.value                          # exp.value : r2 = x[i+1] + y[i]

      arrays.each { |v|                         # v         : x[i+1]     v[i]
        var   = v.child[0].value                # var       : x          v
        index = v.child[2].value                # index     : i+1        i
        if index =~ / [^\w\[\]] /x
          qv = "#{var}_id#{index.intern.to_i}_" # qv     : x_id12345
        else
          qv = "#{v.value}"                     # qv      :            v[i]
        end
        ev = Regexp.escape(v.value)             # ev        : i\+1       i
        qexp = qexp.gsub(/#{ev}/, qv)           # qexp      : r2 = x[id12345] + y[i]
      }

      pderefs.each { |v|                        # v    : [ *, cast_exp ]
        index = v.child[1].value
        qv = "ptr#{index.intern.to_i}_"
        ev = Regexp.escape(v.value)
        qexp = qexp.gsub(/#{ev}/, qv)
      }

      qexp = qexp.gsub(/ [\]\[]+ /x, '_')        # array[i]         -> array_i_
      qexp = qexp.gsub(/ [.] (?= [a-zA-Z_]) /xms, '__') # struct.member    -> struct__member
      qexp = qexp.gsub(/ -> /x, '___')           # structp->member  -> structp___member
      qexp = qexp.gsub(/ [&] /x, 'adr_') 
#      qexp = qexp.gsub(/ [*] /x, 'ptr_') 

      qexp
    end

    def generate_var_decl
      h = @gfor_handler
      buf = ''
      h.ip.each {|var|
        @vari.push exp2qexp(var)
      }
      h.shared_ro.each {|var|
        @vari.push exp2qexp(var)
      }
      h.jp.each {|var|
        @varj.push exp2qexp(var)
      }
      @varj.push "dummy" if h.jloop_desc[:singleloop]

      h.result.each {|var|
        @varf.push exp2qexp(var)
      }

      tagnames = %w( VARI VARJ VARF )
      [@vari, @varj, @varf].each { |vars|
        tagname = tagnames.shift + ' '
        next if vars.empty?
        buf << tagname
        buf << vars.join(', ')
        buf << ";\n"
      }
      buf
    end

    def generate_func_def
      buf = ''
      @gdefun_handlers.each{ |h|
        params = []
        funcname = h.name
        next unless @gfor_handler.funcall.include? funcname # omit unused functions.
        if @precompiled_funcs.include? funcname
          vinfo("A precompiled implementation exists for : #{funcname}()")
          vinfo("Omit its definition                 : #{h.bodynode.value[0..63]}...")
          next
        end
        vinfo("Generating an LSUMP function...     : #{funcname}")
        param_defs = h.argnode.recursive_select(:param_def)
        param_defs.each { |param_def|
          params.concat param_def.recursive_select(:variable)
        }
        buf << "\nfunction #{funcname}(#{params.values(', ')}) {\n"

        # ad hoc care to cheat LSUMP.
        #
        h.private.each { |v|
          next if v.value[-1..-1] == '_'
          buf << "    #{exp2qexp(v)} = 0.0;\n"
        }
        h.bodynode.child.each { |node|
          tmpbuf, omitted = convert_frontend_exp_to_q(node)
          buf << ' ' * 4 << tmpbuf unless tmpbuf.empty?
          if omitted
            case node.symbol
            when :return_stmt
              unless include_invalid_funcall(node)
                buf << ' ' * 4 << "res = #{exp2qexp(node.child[1])};\n"
                vinfo("Return statement recognized         : #{node.value}")
              end
            else
              vwarn("Omit unsupported statement          : #{node.value}") if omitted
            end
          end
        }
        buf << "}\n"
        vinfo("An LSUMP function generated         : #{funcname}")
      }
      buf
    end

    def generate_body
      vinfo("Generating an LSUMP kernel...       : #{@gfor_handler.kernelnode.value[0..63]}...")
      buf = "\n"

      # ad hoc care to cheat LSUMP.
      #
      buf << "#{@vari[0]} = #{@vari[0]} + dummy;\n" if @gfor_handler.jloop_desc[:singleloop]
      @gfor_handler.private.each { |v|
        next if v.value[-1..-1] == '_'
        buf << "#{exp2qexp(v)} = 0.0;\n"
      }

      @gfor_handler.kernelnode.child.each { |c|
        tmpbuf, omitted = convert_frontend_exp_to_q(c)
        buf << tmpbuf unless tmpbuf.empty?
        vwarn("Omit statement                      : #{c.value}") if omitted
      }
      vinfo("An LSUMP kernel generated.")
      buf
    end

    def convert_frontend_exp_to_q(stmtnode)
      buf = ''
      omitted = false

      if include_invalid_funcall(stmtnode)
        omitted = true
        return '', omitted
      end

      case stmtnode.symbol
      when :defvar
        init_decls = stmtnode.recursive_select(:init_decl)
        if init_decls.empty?
          omitted = true
          vinfo("Useless variable declaration        : #{stmtnode.value}")
        else
          init_decls.each { |init_decl|
            declval = exp2qexp(init_decl.child[0])
            initval = exp2qexp(init_decl.child[1])
            buf << "#{declval} = #{initval};\n"
            vinfo("Variable initialization recognized  : #{init_decl.value}")
          }
        end
      when :exp_stmt
        tmpbuf, omitted = convert_exp_stmt_to_q(stmtnode.child[0])
        buf << tmpbuf unless omitted
      when :if_stmt
        omitted = true
        vwarn("Omit 'if' statement                 : #{stmtnode.value}")
      when :for_stmt
        omitted = true
        vwarn("Omit 'for' statement                : #{stmtnode.value}")
      else
        omitted = true
      end
      return buf, omitted
    end

    def convert_exp_stmt_to_q(node)
      buf = ''
      omitted = false

      case node.symbol
      when :assign_exp
        if node.child[2].symbol == :cond_exp
          canonical, qexp = convert_cond_exp_to_q(node.child[2])
          if canonical
            vinfo("Conditional expression recognized   : #{node.value}")
            buf << exp2qexp(node.child[0]) << " " << node.child[1].value << " #{qexp};\n" # dst = qexp;
          else
            omitted = true
            vwarn("Omit conditional expression         : #{node.value}")
            vwarn("  Not in LSUMP-canonical format.")
          end
        elsif node.child[1].value == '=' and @gfor_handler.result.include_value? node.child[0]
          # pure assignment for result var.
          #   a = var;  -> a = 0.0; a += var;
          clr_node = node.dup_tree
          clr_node.child[2] = TNode.new(:DEC_NUMBER, '0.0', 0, '')
          clr_node.recursive_reconstruct_value
          buf << "#{exp2qexp(clr_node)};\n"
          accum_node = node.dup_tree
          accum_node.child[1].value = '+='
          accum_node.recursive_reconstruct_value
          buf << "#{exp2qexp(accum_node)};\n"
          vinfo("Assignment expression to a result var recognized & converted : #{clr_node.value} #{accum_node.value}")
        elsif node.child[1].value == '+=' and not @gfor_handler.result.include_value? node.child[0]
          # abstract assignment for intermediate var.
          #   a += var;  ->  a = a + var;
          binop_val = node.child[1].value[0..0]
          binop_node = TNode.new(binop_val, binop_val, 0, '')
          bin_node = BinaryNode.reduce(:bin_exp, '', node.child[0], binop_node, node.child[2])
          eq_val = "="
          eq_node = TNode.new(eq_val, eq_val, 0, '')
          assign_node = AssignNode.reduce(:assign_exp, '', node.child[0], eq_node, bin_node)
          assign_node.recursive_reconstruct_value
          buf << "#{exp2qexp(assign_node)};\n"
          vinfo("Abstract assignment expression to an intermediate var recognized & expanded : #{assign_node.value}")
        else
          buf << "#{exp2qexp(node)};\n"
          vinfo("Assignment expression recognized    : #{node.value}")
        end
      end
      return buf, omitted
    end

    #
    # Parse cond_exp node and convert it into q expression,
    # if it is in a LSUMP-canonical format.
    # Swap true & false statements, if necessary.
    #
    # The conditional accepted is one of:
    #   a==b  a!=b  a>b  a>=b  a<b  a<=b
    #
    def convert_cond_exp_to_q(condnode)
      canonical = true
      unless condnode.symbol == :cond_exp
        canonical = false 
        vwarn("  Seems not a cond_exp              : #{condnode.value}")
      end
      return canonical, nil unless canonical

      lvar   = exp2qexp(condnode.child[0].child[0])
      rel_op = condnode.child[0].child[1].value
      rvar   = exp2qexp(condnode.child[0].child[2])
      comparator = ''

      case rel_op
      when '==', '!='
        comparator = 'mux_eq'
        if rvar =~ Zero_re
          cond = "#{lvar}"
        elsif lvar =~ Zero_re
          cond = "#{rvar}"
        else
          cond = "#{rvar} - #{lvar}"
        end
      when '>', '>=', '<', '<='
        comparator = 'mux_gt'
        if rvar =~ Zero_re
          cond = "- #{lvar}"
        elsif lvar =~ Zero_re
          cond = "#{rvar}"
        else
          cond = "#{rvar} - #{lvar}"
        end
      else
        vwarn("A relational expression in the cond_exp must be one of: " +
              "<  <= >  >= , but actually not : #{rel_op}")
        return false, nil
      end

      true_exp  = exp2qexp(condnode.child[1])
      false_exp = exp2qexp(condnode.child[2])
      case rel_op
      when '>', '>=', '!='
        tmp = true_exp
        true_exp = false_exp
        false_exp = tmp
      end

      buf = "#{comparator}(#{cond}, #{true_exp}, #{false_exp})"

      return canonical, buf
    end

    def include_invalid_funcall(node)

      # undefined function call.
      #
      funcdefs = @gfor_handler.funcall
      funcalls = node.recursive_select(:funcall_exp)
      funcalls.each { |fc|
        unless funcdefs.include? fc.child[0].value
          vwarn("Omit call to an undefined function  : #{fc.child[0].value}")
          return true
        end
      }

      # unsupported sizeof() operator.
      #
      unless node.recursive_select(:sizeof_exp).empty?
        vwarn("Omit unsupported operator             : sizeof")
        return true
      end

      return false
    end

  end # QGenerator

end # Goose
