module Goose

  class WGenerator

    def initialize(gfor_handlers, qgenerators, infile, prefix, hfiles)
      @gfor_handlers = gfor_handlers
      @qgenerators   = qgenerators
      @infile        = infile
      @prefix        = prefix
      @hfiles        = hfiles
    end

    def generate

=begin
      pragma  = %r{
                   (?: ^ \#pragma \s+ omp \b (?: [^\n] | \\ \n )* \n \s* )*

                   ^ \#pragma \s+ goose \s+ parallel \s+ for \s+
                     (?:
                       (?: \s*
                         \w+
                         \s* \( [^\)]* \)
                       )
                       | \\ \n   # escaped newline
                     )*? \s* \n  # a newline as a terminator

                   (?: ^ \#pragma \s+ omp \b (?: [^\n] | \\ \n )* \n \s* )*

                  }xm
=end
      spc = %r{(?: [ \t] | \\\n)}x
      pragma  = %r{
              (?: ^ \#pragma #{spc}+ omp \b (?: [^\n] | \\ \n )* \n \s* )*
                  ^ \#pragma #{spc}+ goose #{spc}+ parallel #{spc}+ for \b (?: [^\n] | \\ \n )* \n \s*
              (?: ^ \#pragma \s+ omp \b (?: [^\n] | \\ \n )* \n \s* )*
              }xm

      src = read_c()
      rest = ''
      newsrc = ''
      @hfiles.each { |hfile|
        newsrc << %Q(#include "#{hfile}"\n)
      }
      newsrc << %Q(#include "singutil.h"\n)
      qgens = @qgenerators
      @gfor_handlers.each_with_index { |h, i|
        if src =~ pragma
          # look for an i-loop.
          #
          newsrc << $`
          rest = $'
          imatched, pre, icond, ibody, rest = match_for_loop(rest)
          raise "A Goose pragme not followed by a for loop." unless imatched
          icanonical, icnt, icnt_init, icnt_final, istride = parse_for_cond(icond)
          raise "A for-loop not recognized as a Goose-canonical form." unless icanonical
          raise "A for-loop has a loop-counter other than icnt." unless icnt == h.icnt.value
          newsrc << pre
          src = rest

          # newsrc : final output of this method.
          # icond  : i-loop conditional exp,
          #          the outermost parenthesis stripped.
          # ibody  : i-loop body (compound statement),
          #          the outermost curly brackets stripped.
          # rest   : rest of src following the i-loop.

          # look for a j-loop.
          #
          jheader = ''
          jfooter = ''
          found_jloop = false
          until ibody.empty?
            jmatched, jpre, jcond, jbody, jrest = match_for_loop(ibody)
            break unless jmatched
            ibody = jrest
            jcanonical, jcnt, jcnt_init, jcnt_final, jstride = parse_for_cond(jcond)
            if jcanonical and jcnt == h.jcnt.value
              vinfo("Found j-loop : for (#{jcond})")
              found_jloop = true
              jheader << jpre
              # jbody discarded since it is replaced with API calls.
            elsif found_jloop
              jfooter << jpre << "\nfor (#{jcond}) {" << jbody << "}\n"
            else
              jheader << jpre << "\nfor (#{jcond}) {" << jbody << "}\n"
            end
          end
          vinfo("No j-loop found. The entire i-loop body is used as a kernel.") unless found_jloop
          newsrc << "for (#{icond}) {" << jheader << "}\n" unless (jheader =~ /\A \s* \z/xms or h.jloop_desc[:singleloop])
          qgen = qgens.shift
          newsrc << api_calls(h, i, qgen)
          jfooter << ibody
          newsrc << "for (#{icond}) {" << jfooter << "}\n" unless (jfooter =~ /\A \s* \z/xms or h.jloop_desc[:singleloop])
        else
          raise "'#pragma goose parallel for' for Gfor[#{i}] not found." # cannot happen.
        end
      }
      newsrc << rest
    end

    #
    # read C source file.
    # directives other than '#include' are preprocessed by 'cpp -P'.
    # '#include' directives are remain unchanged.
    # '#define' directives are evaluated but not consumed.
    # They are re-evaluated when wfiles are passed on to gcc.
    # 
    def read_c
      tmpfile = "tmp#{$$}"
      buf = open(@infile, "r").read
      buf = buf.gsub(/ ^\#include /xms, '#pragma include')
      buf = buf.gsub(/ (^\#define [^\n]*? \n ) /xms) { "#pragma #{$1}\n#{$1}" }
      open("| cpp -P > #{tmpfile}", "w") { |f| f << buf }
      buf = open("#{tmpfile}", "r").read
      File.unlink("#{tmpfile}")
      buf = buf.gsub(/ ^\#pragma \s+ include /xms, '#include')
      buf = buf.gsub(/ ^\#pragma \s+ \#define /xms, '#define')
      buf
    end

    #
    # pre  : src preceding the match
    # cond : for-loop conditional exp (i.e. (exp;exp;exp) ),
    #        the outermost parenthesis stripped.
    # body : for-loop body (i.e. compound statement),
    #        the outermost curly brackets stripped.
    # rest : src following the match.
    #
    def match_for_loop(src)
      # match 'for'.
      #
      if src !~ / \b for \b \s* /x
        matched = false
        return matched, nil, nil, nil
      end

      # match (exp; exp; exp)
      #
      pre = $`
      rest = $'
      matched, pre0, cond, rest0 = rest.balanced_c_exp('(', ')', true)
      return matched, nil, nil, nil unless matched
      rest = rest0

      # check if immediately followed by { statement(s?) } or not.
      #
      if rest =~ / \A \s* (?= \{ ) /xms
        matched, pre0, body, rest0 = rest.balanced_c_exp('{', '}', true)
        if (matched)
          rest = rest0
          return matched, pre, cond, body, rest
        end
      end

      # match a single exp.
      #
      if rest =~ / [^;]*?; /xms
        matched = true
        body = $&
        rest = $'
        return matched, pre, cond, body, rest
      end

      return matched, nil, nil, nil unless matched

    end

    def parse_for_cond(cond)
      canonical = true
      if cond !~ / \s*([^;]*)\s* ;  \s*([^;]*)\s* ;  \s*([^;]*)\s* /x
        canonical = false
        vputs(1, "#{cond} : Malformed for-loop statement.")
      end
      cond0, cond1, cond2 = $1, $2, $3

      if cond0 !~ /(\w+) \s* = \s* ([^;]+) \s* /x
        canonical = false
        vputs(1, "#{cond0} : Not recognized as a Goose-canonical for-loop.")
      end
      cnt, cnt_init = $1, $2

      if cond1 !~ / \s* #{cnt} \s* < \s* ([^=;]+) \s* /x
        canonical = false
        vputs(1, "#{cond1} : Not recognized as a Goose-canonical for-loop.")
      end
      cnt_final = $1

      if cond2 =~ / \s* #{cnt} \s* \+\+ \s*$/xms                           # i++
        stride = 1.to_s
      elsif cond2 =~ / \s* #{cnt} \s* \+= \s* ([^;]+) /xms                 # i+= val
        stride = $1
      elsif cond2 =~ / \s* #{cnt} \s* = \s* #{cnt} \s* \+ \s* ([^;]+) /xms   # i = i + val
        stride = $1
      elsif cond2 =~ / \s* #{cnt} \s* = \s* ([^;]+) \s* \+ \s* #{cnt} /xms   # i = val + i
        stride = $1
      else
        canonical = false
        vputs(1, "#{cond2} : Not recognized as a Goose-canonical for-loop.")
      end

      vputs(3, "canonical : #{canonical}")
      vputs(3, "cond      : #{cond}")
      vputs(3, "cnt       : #{cnt}")
      vputs(3, "cnt_init  : #{cnt_init}")
      vputs(3, "cnt_final : #{cnt_final}")
      vputs(3, "stride    : #{stride}")
      vputs(3, "\n")

      return canonical, cnt, cnt_init, cnt_final, stride
    end

    def api_calls(gforh, i, qgen)
      icnt     = gforh.icnt.value
      jcnt     = gforh.jcnt.value
      i_stride = gforh.iloop_desc[:stride]
      i0       = gforh.iloop_desc[:cnt_init]
      ni       = gforh.iloop_desc[:cnt_final]
      npack    = gforh.nip_pack
      j0       = gforh.jloop_desc[:cnt_init]
      nj       = gforh.jloop_desc[:cnt_final]
      nj_write = gforh.njp_write
      nj_write = nj unless nj_write
      dpara    = gforh.devpara
      prefix   = "#{@prefix}_#{i}"
      wtmp     = @@wtemplate.dup

      # care for API-call prefix.
      wtmp = wtmp.gsub(/%prefix_sing%/, "#{prefix}")
      wtmp = wtmp.gsub(/%prefix_grape%/, "#{prefix}_grape")
      if @gfor_handlers.size == 1
        wtmp = wtmp.gsub(/%sing_open_begin%\n/xms, '')
        wtmp = wtmp.gsub(/%sing_open_end%\n/xms, '')
        wtmp = wtmp.gsub(/%sing_close_begin%\n/xms, '')
        wtmp = wtmp.gsub(/%sing_close_end%\n/xms, '')
      else
        wtmp = wtmp.gsub(/ %sing_open_begin% .*? %sing_open_end% \n /xms, "#{prefix}_grape_init();")
        wtmp = wtmp.gsub(/ %sing_close_begin% .*? %sing_close_end% \n /xms, "SING_close();")
      end

      # care for devpara.
      case dpara
      when /\A [iI] \z/xms
        wtmp = wtmp.gsub(/%idpara_begin%\n/, '')
        wtmp = wtmp.gsub(/%idpara_end%\n/, '')
        wtmp = wtmp.gsub(/ %jdpara_begin% .*? %jdpara_end% /xms, '')
      when /\A [jJ] \z/xms
        wtmp = wtmp.gsub(/%jdpara_begin%\n/, '')
        wtmp = wtmp.gsub(/%jdpara_end%\n/, '')
        wtmp = wtmp.gsub(/ %idpara_begin% .*? %idpara_end% /xms, '')
      else
        wtmp = wtmp.gsub(/ %idpara_begin% .*? %idpara_end% /xms, '')
        wtmp = wtmp.gsub(/ %jdpara_begin% .*? %jdpara_end% /xms, '')
      end
      case dpara
      when /\A [iI] \z/xms, /\A [jJ] \z/xms
        wtmp = wtmp.gsub(/%savedpara_begin%\n/, '')
        wtmp = wtmp.gsub(/%savedpara_end%\n/, '')
        wtmp = wtmp.gsub(/%restoredpara_begin%\n/, '')
        wtmp = wtmp.gsub(/%restoredpara_end%\n/, '')
      else
        wtmp = wtmp.gsub(/ %savedpara_begin% .*? %savedpara_end% /xms, '')
        wtmp = wtmp.gsub(/ %restoredpara_begin% .*? %restoredpara_end% /xms, '')
      end

      #
      # pack/unpack variables.
      #
      wtmp = wtmp.gsub(/%j0%/, j0.to_s)
      wtmp = wtmp.gsub(/%nj%/, nj.to_s)
      wtmp = wtmp.gsub(/%nj_write%/, nj_write.to_s)
      wtmp = wtmp.gsub(/%subj0%/, (j0.to_i == 0) ? "" : "- #{j0.to_s}")

      wtmp = wtmp.gsub(/%i0%/, i0.to_s)
      wtmp = wtmp.gsub(/%ni%/, ni)

      #
      # i_stride    npack    i_stride_npack
      # -----------------------------------
      # "1"          1       ""
      # digit        *       i_stride*npack
      # var          1       var
      # var         >1       var*npack
      #
      if i_stride == "1" and npack == 1
        i_stride_npack = ""
      elsif i_stride =~ /\A \d+ \z/xms
        i_stride_npack = "#{i_stride.to_i * npack}"
      else
        if npack == 1
          i_stride_npack = "#{i_stride}"
        else
          i_stride_npack = "(#{i_stride} * #{npack})"
        end
      end
      wtmp = wtmp.gsub(/%mul_istride%/) {
        if i_stride == "1" and npack == 1
          ""
        else
          " * #{i_stride_npack}"
        end
      }
      wtmp = wtmp.gsub(/%limitnn%/) {
        if i_stride == "1" and npack == 1
          "nn = #{ni} - i;"
        else
          "nn = (#{ni} - i - 1) / #{i_stride_npack} + 1;"
        end
      }

      # pack jp
      qjp = qgen.varj
      str0, str1 = '', ''
      gforh.jp.push VarrefExpNode.new2("dummy") if gforh.jloop_desc[:singleloop]
      gforh.jp.each { |v|
        qvar = qjp.shift
        var = v.value
        if var =~ /\A #{jcnt} \z/xms
          var = "#{jcnt}+jj"
        else
          var = var.gsub(/\[ (.*) \b #{jcnt} \b (.*?) \]/x ) {
            "\[#{$1}#{jcnt}+jj#{$2}\]"
          }
        end
        var = 0.0  if gforh.jloop_desc[:singleloop]
        str0 << "jp[jj].#{qvar}[vid] = #{var};\n"
        str1 << "jp[jj].#{qvar}[vid] = 0.0;\n"
      }
      if str0.empty?
        wtmp = wtmp.gsub(/ %packjp_begin% .*? %packjp_end% /xms, '')
      else
        wtmp = wtmp.gsub(/%packjp0%\n/, str0)
        wtmp = wtmp.gsub(/%packjp1%\n/, str1)
        wtmp = wtmp.gsub(/%packjp_begin%\n/, '')
        wtmp = wtmp.gsub(/%packjp_end%\n/, '')
      end
      if nj == nj_write
        wtmp = wtmp.gsub(/ %use_nj_write_begin% .*? %use_nj_write_end% /xms, '')
      else
        wtmp = wtmp.gsub(/%use_nj_write_begin%\n/, '')
        wtmp = wtmp.gsub(/%use_nj_write_end%\n/, '')
      end

      # pack ip
      qip = qgen.vari
      str = gforh.ip.inject('') { |memo, v|
        qvar = qip.shift
        var = v.value
        if npack == 1
          i_delta = i_stride == "1" ? "ii" : "ii*#{i_stride}"
        else
          i_delta = i_stride == "1" ? "ii*#{npack}" : "ii*#{i_stride}*#{npack}"
        end
        if var =~ /\A #{icnt} \z/xms
          var = "#{icnt}+#{i_delta}"
        else
          var = var.gsub(/\[ (.*) \b #{icnt} \b (.*?) \]/x ) {
            "\[#{$1}#{icnt}+#{i_delta}#{$2}\]"
          }
        end
        memo << "ip[ii].#{qvar} = #{var};\n"
      }
      str << gforh.shared_ro.inject('') { |memo, v|
        qvar = qip.shift
        memo << "ip[ii].#{qvar} = #{v.value};\n"
      }
      wtmp = wtmp.gsub(/%packip%\n/, str)

      # unpack result
      qresult = qgen.varf
      str = gforh.result.inject('') { |memo, v|
        qvar = qresult.shift
        var = v.value
        if npack == 1
          i_delta = i_stride == "1" ? "ii" : "ii*#{i_stride}"
        else
          i_delta = i_stride == "1" ? "ii*#{npack}" : "ii*#{i_stride}*#{npack}"
        end
        var = var.gsub(/\[ (.*) \b #{icnt} \b (.*?) \]/x ) {
          "\[#{$1}#{icnt}+#{i_delta}#{$2}\]"
        }

        if gforh.jloop_desc[:singleloop]
          if v.iotype == :result_shared
            memo << "#{var} += result[ii].#{qvar} / nbb;\n"
          else
            memo << "#{var} = result[ii].#{qvar} / nbb;\n"
          end
        else
          if v.iotype == :result_shared
            memo << "#{var} += result[ii].#{qvar};\n"
          else
            memo << "#{var} = result[ii].#{qvar};\n"
          end
        end
      }
      wtmp = wtmp.gsub(/%unpackresult%\n/, str)

      wtmp
    end

    @@wtemplate = <<-END_OF_WTEMPLATE
{
    enum {
        npe = 32,
        nbb = 16,
        nvec = 4,
    };

    static struct %prefix_grape%_j_particle_struct jp[nbb];
    static struct %prefix_grape%_i_particle_struct ip[npe*nvec];
    static struct %prefix_grape%_result_struct result[npe*nvec];
    int npipe, nn;
    int i, ii, j, jj, vid;

%savedpara_begin%
    Devpara dpara_saved = SING_devpara();
%savedpara_end%
%idpara_begin%
    SING_set_devpara(I_DEVPARA);
%idpara_end%
%jdpara_begin%
    SING_set_devpara(J_DEVPARA);
%jdpara_end%

%sing_open_begin%
    static int firstcall = 1;
    if (firstcall) {
       %prefix_grape%_init(); \/\/ open only once in a run.
       firstcall = 0;
    }
%sing_open_end%

    npipe = npe * nvec;
    if (SING_devpara() == I_DEVPARA) {
        npipe *= SING_ndevice();
    }

%packjp_begin%
    for (j = %j0%; j < %nj_write%; j += nbb) {
        for (jj = 0; jj < nbb; jj++) {
            for (vid = 0; vid < nvec; vid++) {
                if (j + jj < %nj%) {
                    %packjp0%
                }
                else {
                    %packjp1%
                }
            }
        }
        SING_EM_write(0, j %subj0%, nbb, jp);
    }

%use_nj_write_begin%
    for (j = %j0%; j < %nj%; j += nbb);
%use_nj_write_end%

    for (i = 0; i < SING_ndevice(); i++, j += nbb) {
        SING_EM_write(0, j %subj0%, nbb, jp);
    }
%packjp_end%

    for (i = %i0%; i < %ni%; i += npipe%mul_istride%) {
        if ((i + npipe%mul_istride%) > %ni%) {
            %limitnn%
        }
        else {
            nn = npipe;
        }
        for (ii = 0; ii < nn; ii++) {
            %packip%
        }
        %prefix_sing%_send_vsm_constants();
        %prefix_sing%_register_i_particle_conversion();
        %prefix_sing%_send_i_particle(ip, nn);
        %prefix_sing%_grape_run((%nj% %subj0% - 1) / nbb + 1);
        %prefix_sing%_get_result(result);
        for (ii = 0; ii < nn; ii++) {
            %unpackresult%
        }
    }

%restoredpara_begin%
    SING_set_devpara(dpara_saved);
%restoredpara_end%

%sing_close_begin%
   // SING_close(); // do not close until the run finishes.
%sing_close_end%
}
    END_OF_WTEMPLATE

  end # QGenerator

end # Goose


class String

  #
  # extract the outermost balanced token list.
  # separators can be any string other than double quote, such as ( and ), /* and */.
  # returns: matched_or_not, pre_matched_str, matched_str, post_matched_str
  # 
  # In the C grammer, double quote cannot be nested.
  # Therefor, in order to match a double-quoted string,
  # a simple regexp /\A " (?: [^"] | \\" )* " /xms will do.
  #
  def balanced_c_exp(lsep, rsep, strip=false)
    str = self
    lsep = Regexp.escape(lsep)
    rsep = Regexp.escape(rsep)
    #    puts "lsep:#{lsep}  rsep:#{rsep}  str:#{str}"

    # split into tokens (i.e. separators and other substrings).
    #
    tokens = []
    while not str.empty?
      case str
      when /\A " (?: [^"] | \\" )* " /xms
        str = $'
        t = $&
        if tokens[-1] and tokens[-1] !~ / \A (?: #{lsep} | #{rsep}) \z /xms
          tokens[-1].concat t
        else
          tokens.push t
        end
      when /\A #{lsep} /xms
        str = $'
        tokens.push $&
      when /\A #{rsep} /xms
        str = $'
        tokens.push $&
      when /\A . /xms
        str = $'
        t = $&
        if tokens[-1] and tokens[-1] !~ / \A (?: #{lsep} | #{rsep}) \z /xms
          tokens[-1].concat t
        else
          tokens.push t
        end
      else
        puts "no match."
        str = ''
      end
    end

#    puts "tokens : #{tokens.join(' | ')}"

    # parse the tokens.
    #
    matched  = false
    pre      = ''
    body     = ''
    post     = ''
    sepstack = []

    # pre match
    until tokens.empty?
      t = tokens.shift
      if t =~ / \A #{lsep} \z/xms
        body << t unless strip
        sepstack.push t
        break
      end
      pre << t
    end

    # body
    until tokens.empty?
      t = tokens.shift
      case t
      when / \A #{lsep} \z /xms
        body << t
        sepstack.push t
      when / \A #{rsep} \z /xms
        sepstack.pop
        if sepstack.empty?
          matched = true
          body << t unless strip
          break
        else
          body << t
        end
      else
        body << t
      end
    end

    post = tokens.join
    return matched, pre, body, post
  end

  def strip_balanced_c_exp(lsep, rsep)
    str = self.strip
    matched, pre, body, post = str.balanced_c_exp(lsep, rsep, false)
    if matched and body == str
      elsep = Regexp.escape(lsep)
      ersep = Regexp.escape(rsep)
      matched, pre, str, post = str.balanced_c_exp(lsep, rsep, true)
#      str = str.gsub(/\A #{elsep} (.*) #{ersep} \z/x) { $1 }
    end
    str
  end

end # String
