module Goose

  #
  # A handler for a C-source fragment for which '#pragma goose' given.
  #
  class GHandler

    def flatten(node)
      node.child.compact.each { |c| 
        unroll_for_stmt(c)
      }
      node.refine

      node.child.compact.each { |c| 
        flatten_if_stmt(c)
      }
      node.refine

      newchild = []
      node.child.compact.each { |c| 
        if c.symbol == :exp_stmt
          newchild.concat flatten_exp_stmt(c)
        else
          newchild.push c
        end
      }
      node.child = newchild
      node.refine
    end

    private

    def unroll_for_stmt(node)
      node.child and node.child.compact.each { |c|
        unroll_for_stmt(c)
      }
      return unless node.symbol == :for_stmt
      canonical, desc = parse_for_stmt(node)
      unless canonical
        vwarn("Could not unrolled a non-canonical 'for' statement: #{node.value}")
        return
      end
      dk  = desc[:stride].to_i
      k0  = desc[:cnt_init].to_i
      k1  = desc[:cnt_final].to_i
      rel = desc[:rel_op]
      indices = []
      k = k0
      while ( eval "k #{rel} k1" )
        indices.push k
        k += dk
      end
      node.unroll(desc[:cnt], indices)
      vinfo("Unrolled a 'for' statement: #{node.value}")
    end

    #
    # flatten nested if_stmt(s) in a node of arbitrary type.
    #
    def flatten_if_stmt(node)
      node.child and node.child.compact.each { |c|
        flatten_if_stmt(c)
      }
      return unless node.symbol == :if_stmt
      node.flatten
#      vinfo("Flatten an 'if' statement: #{node.value}")
    end

    #
    # flatten nested cond_exp(s) in an exp_stmt node.
    #
    def flatten_exp_stmt(node)
      flatten_nodes = []
      ns = node.flatten
      flatten_nodes.concat ns
      vinfo("Flatten an expression: #{node.value}") if ns.size > 2
#      puts "!!! fs : #{flatten_nodes.values}"
      flatten_nodes
    end

    #
    # parse for-statement node.
    # set a descriptor ('desc') if it is in a Goose-canonical for-loop format.
    # returns canonical_or_not and a descriptor.
    # desc : {cnt, cnt_init, cnt_final, rel_op, cnt_stride}
    #
    def parse_for_stmt(fsnode)
      canonical = true
      canonical = false unless fsnode.symbol == :for_stmt
      canonical = false unless fsnode.child[0].symbol == :assign_exp
      canonical = false unless fsnode.child[0].child[1].value == '='
      cnt = fsnode.child[0].child[0].value

      canonical = false unless fsnode.child[1].symbol == :rel_exp
      canonical = false unless fsnode.child[1].child[0].value == cnt
      canonical = false unless fsnode.child[1].child[1].value =~ /(?: < | <=  )/xms # <

      case fsnode.child[2].value
      when / \s* #{cnt} \s* \+\+ \s*$/xms  # i++
        stride = 1.to_s
      when / \s* #{cnt} \s* \+= \s* ([^;]+) /xms   # i += val
        stride = $1
      when / \s* #{cnt} \s* = \s* #{cnt} \s* \+ \s* ([^;]+) /xms   # i = i + val
        stride = $1
      when / \s* #{cnt} \s* = \s* ([^;]+) \s* \+ \s* #{cnt} /xms   # i = val + i
        stride = $1
      else
        canonical = false
      end

      desc = {}
      if canonical
        desc[:cnt]       = cnt
        desc[:cnt_init]  = fsnode.child[0].child[2].value
        desc[:rel_op]    = fsnode.child[1].child[1].value
        desc[:cnt_final] = fsnode.child[1].child[2].value
        desc[:stride]    = stride
      end

      return canonical, desc
    end

  end # GHandler

  #
  # A handler for a C-source fragment for which '#pragma goose parallel for' given.
  #
  class GforHandler < GHandler

    attr_reader :icnt, :jcnt, :ip, :jp, :shared_ro, :private, :result,
    :kernelnode, :iloop_desc, :jloop_desc, :funcall,
    :precision, :devpara, :asmfile, :njp_write, :nip_pack

    def initialize(gdefun_handlers, precompiled_funcs)
      @result      = []
      @ip          = []
      @jp          = []
      @njp_write   = nil
      @shared_ro   = []
      @private     = []
      @icnt        = VarrefExpNode.new2('i')
      @jcnt        = VarrefExpNode.new2('j')
      @precision   = 'double'
      @devpara     = nil
      @asmfile     = nil
      @nip_pack    = 1
      @funcall     = precompiled_funcs
      @iloop_desc  = nil
      @jloop_desc  = nil
      @gdefun_handlers   = gdefun_handlers
    end # initialize

    def load(topnode)
      @topnode   = topnode
      gfor_args_node = topnode.child[1]
      expand_twodots(gfor_args_node)
      init_gfor_params(gfor_args_node)
      @iloopnode = topnode.child[2]
      canonical, @iloop_desc = parse_for_stmt(@iloopnode) # gfor_stmt' for_stmt
      raise "i-loop not recognized as a Goose-canonical for-loop." unless canonical
      jloopnode, @jloop_desc = select_jloop(@iloopnode.child[3]) # iloop's :stmt
      @kernelnode = jloopnode ? jloopnode.child[3] : @iloopnode.child[3]
      @jloop_desc[:singleloop] = jloopnode ? false : true
    end

    def analyze
      type_vars(@kernelnode.refered_vars)
      var_dependency_list
    end

    def diagnostics
      puts "Diagnostics:"
      puts "    pragma      : #{@topnode.child[1].value}"
      puts "    result      : #{@result.values}"
      shared_results = @result.select { |v| v.iotype == :result_shared }
      puts "      ( shared  : #{shared_results.values} )" unless shared_results.empty?
      puts "    ip          : #{@ip.values}"
      puts "    jp          : #{@jp.select { |v| v.value != 'dummy'}.values }"
      puts "    shared_ro   : #{@shared_ro.values}"
      puts "    private     : " << (verbose > 2 ? "#{@private.values}" : "#{@private.select { |v| v.value[-1..-1] != '_' }.values}")
      puts "    icnt        : #{@icnt.value}"
      puts "    jcnt        : #{@jcnt.value}"
      puts "    precision   : #{@precision}"
#      puts "    devpara     : #{@devpara ? @devpara : "not specified"}"
      puts "    asmfile     : #{@asmfile ? @asmfile : "not given"}"
      puts "    nip_pack    : #{@nip_pack}"
      puts "    njp_write   : #{@njp_write ? @njp_write : "not given"}"
      puts "    funcall     : #{@funcall.join(' ')}"
    end

    def flatten(node)
      unroll_kernel
      super(node)
    end

    private

    def unroll_kernel
      return if @nip_pack == 1

      stride = iloop_desc[:stride]
      unrolled_nodes = []
      (0..(@nip_pack-1)).each { |i|
        duped_knode = @kernelnode.dup_tree
        unless i == 0
          #
          #  stride      i       offnode
          #  ----------------------------
          #  digit       *       :DEC_NUMBER, stride*i
          #  var          1      :variable, var
          #  var         >1      :mul_exp, var*i
          #
          if stride =~ /\A \d+ \z/xms
            offnode = TNode.new(:DEC_NUMBER, (stride.to_i * i).to_s, 0, '')       # 6
          elsif i == 1
            offnode = VarrefExpNode.new2(stride)                                  # ns
          else
            snode   = VarrefExpNode.new2(stride)                                  # ns
            mulnode = TNode.new('*', '*', 0, '')                                  # *
            inode   = TNode.new(:DEC_NUMBER, i.to_s, 0, '')                       # 3
            offnode = BinaryNode.reduce(:mul_exp, '', snode, mulnode, inode)      # ns * 3
          end
          addnode = TNode.new('+', '+', 0, '')                                    # +
          idnode  = VarrefExpNode.new2(@icnt.value)                               # i
          newidnode = BinaryNode.reduce(:add_exp, '', idnode, addnode, offnode)   # i + offnode
          newidnode.refine
          duped_knode.child.each { |c| c.recursive_sub(@icnt.value, newidnode) }  # i  ->  i+offnode
        end
        unrolled_nodes.concat duped_knode.child
      }
      @kernelnode.child = unrolled_nodes
      @kernelnode.refine
      vinfo("Unrolled the kernel.")
    end

    #
    # expand [n0..n1] notations.
    #
    def expand_twodots(gargs)                     # gargs : ip, jp, result
      gargs.child && gargs.child.each { |garg|    # garg  : ip (x[i][0..1][0..2], y[i][0..1], z[i])
        found_twodot = true
        while found_twodot
          parent = garg.child[1]
          params = parent.child                   # params : x[i][0..1][0..2], y[i][0..1], z[i]
          found_twodot = false
          params.each { |param|                   # param  : x[i][0..1][0..2]
            duped_params = param.expand_twodots   # new elements added to the params at this point.
            next unless duped_params
            found_twodot = true
            break                                 # should not continue params#each anymore, since params modified.
          }
          parent.refine
        end
      }
    end

    #
    # Look for j-loop statements in child array of 'rootnode'.
    # Return the loop if exactly one j-loop found.
    # Raise an exception otherwise.
    #
    def select_jloop(rootnode)
      desc = {}
      jloopnodes = []
      rootnode.child.each { |c| 
        next unless c.symbol == :for_stmt
        canonical, desc = parse_for_stmt(c)
        jloopnodes.push c if canonical and desc[:cnt] == @jcnt.value
      }
      raise "Multiple j-loops found." if jloopnodes.size > 1
      return jloopnodes[0], desc if jloopnodes.size == 1
      
      vinfo("No j-loop found. The entire i-loop body is used as a kernel.")
      return nil, { :stride => "1", :cnt_init => "0", :cnt_final => "1", :rel_op => "<" }
    end
    
    #
    # init gfor parameters according to 'goose parallel for pragma'.
    #
    def init_gfor_params(gargs)
      gargs.child && gargs.child.each { |garg|
        params = garg.child[1].child # gfor_param
        case garg.symbol
        when :gfor_result
          @result.concat params
        when :gfor_ip
          @ip.concat params
        when :gfor_jp
          @jp.concat params
        when :gfor_shared_ro
          @shared_ro.concat params
        when :gfor_private
          @private.concat params
        when :gfor_icnt
          raise "Multiple icnt given in Goose pragma." if params.size > 1
          @icnt = *params
        when :gfor_jcnt
          raise "Multiple jcnt given in Goose pragma." if params.size > 1
          @jcnt = *params
        when :gfor_precision
          raise "Multiple precision given in Goose pragma." if params.size > 1
          @precision = params[0].value
        when :gfor_devpara
          raise "Multiple devpara given in Goose pragma." if params.size > 1
          @devpara = params[0].value
        when :gfor_asmfile
          raise "Multiple asmfile given in Goose pragma." if params.size > 1
          @asmfile = params[0].value
        when :gfor_njp_write
          raise "Multiple njp_write given in Goose pragma." if params.size > 1
          @njp_write = params[0].value
        when :gfor_nip_pack
          raise "Multiple nip_pack given in Goose pragma." if params.size > 1
          @nip_pack = params[0].value.to_i
        else
          raise "Invalid tag in Goose pragma : symbol:#{garg.symbol} value:#{garg.value}"
        end
      }

    end # init_gfor_params

    #
    # categorize vars in j-loop.
    #
    def type_vars(vars)

      # Picks up function calls.
      #
      defuns = @gdefun_handlers.inject([]) { |names, h|
        names.push h.name
      }
      available_funcs = @funcall + defuns # precompiled ones or user-defined ones may be invoked.

=begin
      puts "available_funcs:#{available_funcs.join(', ')}"
      puts "vars:#{vars.values}"
=end

      @funcall = []
      vars = vars.select { |var|
        retval = true
        if var.child  # priamary_exp cannot be a function call.
          if available_funcs.include? var.child[0].value
            @funcall.push var.child[0].value
            retval = false 
          end
        end
        retval
      }

      # recognize explicity typed vars.
      #
      untyped_vars = vars.select { |var|
        if @result.include_value? var
          vinfo("Recognized as a result var : #{var.value}")
          false
        elsif @ip.include_value? var
          vinfo("Recognized as an ip var : #{var.value}")
          false
        elsif @jp.include_value? var
          vinfo("Recognized as a jp var : #{var.value}")
          false
        elsif @shared_ro.include_value? var
          vinfo("Recognized as a shared_ro var : #{var.value}")
          false
        elsif @private.include_value? var
          vinfo("Recognized as a private var : #{var.value}")
          false
        elsif var.value == @icnt.value
          vinfo("Recognized as an icnt var : #{var.value}")
          false
        elsif var.value == @jcnt.value
          vinfo("Recognized as a jcnt var : #{var.value}")
          false
        else
          true
        end
      }

      values = verbose > 2 ? "#{untyped_vars.values}" : "#{untyped_vars.select { |v| v.value[-1..-1] != '_' }.values}"
      vwarn("vars not explicitly typed: #{values}") unless values.empty?

      # infer types of vars not declared in the pragma.
      #
      untyped_vars.each { |var|
        islvalue = @kernelnode.lvalue?(var)
        depend_i = (var.recursive_select(@icnt.value) { |node, v| node.value == v }).empty? ? false : true
        depend_j = (var.recursive_select(@jcnt.value) { |node, v| node.value == v }).empty? ? false : true

        if depend_i and depend_j
          raise "Cannot handle '#{var.value}' that depends both on icnt & jcnt."
        end

        if var.symbol == :funcall_exp
          vwarn("Omit an undefined function call: #{var.value}")
        elsif islvalue 
          if var == @icnt or var == @jcnt
            raise "A loop counter (#{var.value}) cannot be modified in the loop."
          elsif depend_i
            vwarn("Inferred as a result var : #{var.value}")
            @result.push var
          elsif depend_j
            raise "'#{var.value}' that depends on jcnt cannot be used as an lvalue."
          else
            if var.value[-1..-1] == '_'
              vinfo("Autogenerated a private var for internal use: #{var.value}")
            else
              vwarn("Inferred as a private var : #{var.value}")
            end
            @private.push var
          end
        else
          if depend_i
            vwarn("Inferred as an ip var : #{var.value}")
            @ip.push var
          elsif depend_j
            vwarn("Inferred as a jp var : #{var.value}")
            @jp.push var
          else
            vwarn("Inferred as a shared_ro var : #{var.value}")
            @shared_ro.push var
          end
        end
      }
    
      @result.each { |var|
        depend_i = (var.recursive_select(@icnt.value) { |node, v| node.value == v }).empty? ? false : true
        var.iotype = :result_shared unless depend_i
      }

    end # type_vars

    #
    # create a var-dependency tree for each 'result' var.
    #
    def var_dependency_list
      vinfo("Result vars dependency:")
      @result.each { |resultnode|
        prs = resultnode.prerequisites(@iloopnode)
        values = verbose > 2 ? "#{prs.values}" : "#{prs.select { |v| v.value[-1..-1] != '_' }.values}"
        vinfo("    '#{resultnode.value}' relies on : #{values}")
      }
    end

  end # GforHandler

  #
  # A handler for a C-source fragment for which '#pragma goose func' given.
  #
  class GdefunHandler < GHandler

    attr_reader :name, :argnode, :bodynode, :parameter, :private

    def initialize
      @argnode  = nil
      @bodynode = nil
    end # initialize

    def load(topnode)
      defunnode = topnode.child[1]
      @argnode  = defunnode.child[1]
      @bodynode = defunnode.child[3]
      @name     = @argnode.recursive_select(:variable)[0].value
    end

    def analyze
      type_vars
    end

    def diagnostics
      puts "Diagnostics:"
      puts "    name        : #{@name}"
      puts "    parameter   : #{@parameter.values}"
      puts "    private     : " << (verbose > 2 ? "#{@private.values}" : "#{@private.select { |v| v.value[-1..-1] != '_' }.values}")
    end

    private

    def type_vars
      @parameter = @argnode.child[2].defined_vars       # function arguments.
      pvals = @parameter.map { |v|
        v.value
      }
      @private   = @bodynode.refered_vars.select { |v|  # variables defined inside the function body.
        not (pvals.include? v.value)
      }
    end

  end # GdefunHandler

end # Goose
