#!/usr/bin/ruby -w

$LOAD_PATH.unshift("#{File.dirname($0)}/../lib/")

require 'English'
require 'optparse'

# Goose modules
require 'utility'
require 'cparser'
require 'syntacticnode'
require 'ghandler'
require 'qgenerator'
require 'wgenerator'

include Utility

#
# parse command-line arguments and then update the application context
# according to the result.
#
def parse_commandline(ac)
  op = OptionParser.new
  op.banner = "usage: #{$0} -i <infile> -a <arch>"
  op.on('-i', '--infile=<fname>',
        'Input source code in C with Goose directive.') { |f| ac[:infile] = f }
  op.on('-a', '--arch=<arch>',
        'Architecture of the hardware accelerator. [gdr]') { |arch| ac[:arch] = arch.intern }
  op.on('-I', '--header-path=<path>',
        'Search path for header files.') { |path| ac[:hpath] = "-I#{path}" }
  op.on('-p', '--prefix=<prefix>',
        'Prefix for the hardware API calls. [ ]') { |p| ac[:prefix] = p }
  op.on('-v', '--verbose[=level]',
        'The higher level gives the more verbose messages. [2]') { |lv| ac[:verbose] = (lv ? lv.to_i : 2) }
  op.on('-h', '--help', 'Print this message.') {
    puts op.help
    exit 0
  }

  begin
    op.parse!
  rescue OptionParser::ParseError => err
    raise ArgumentError, err.message
  end
  unless ac[:infile]
    puts "No input file given."
    puts op.help
    exit 1
  end
  return ac
end

#
# The main routine starts from here.
#
def toplevel
  hw_spec = {
    :gdr => {
      :name  => 'gdr',
      :funcs => %w[ rsqrt pow32 ]
    },
    :ati => {
      :name  => 'ati',
      :funcs => %w[ ]
    },
  }
  app_context = {
    :infile      => nil,
    :wfile       => nil,
    :qfiles      => [],
    :hfiles      => [],
    :arch        => :gdr,
    :hpath       => '',
    :verbose     => 2
  }
  app_context = parse_commandline(app_context) # unparsed args may reside in $ARGV.
  @verbose = app_context[:verbose]

  # prase input C source and create a syntactic tree.
  #
  parser = Goose::Cparser.new
  parser.verbose = @verbose
  parser.yydebug = true if @verbose > 5
  rootnode = parser.parse("|cpp #{app_context[:hpath]} #{app_context[:infile]}")
  rootnode.verbose = @verbose
  rootnode.refine

#  rootnode.dump(0)

  # create a GfuncHandler for each 'goose func' pragma.
  #
  gdefun_handlers = []
  gdefuns = rootnode.recursive_select(:gdefun)
  gdefuns.each_with_index {|gdefun, i|
    gdefun_handlers[i] = Goose::GdefunHandler.new
    gdefun_handlers[i].verbose = @verbose
    gdefun_handlers[i].load(gdefun)
  }
  gdefun_handlers.each_with_index { |h, i|
    h.flatten(h.bodynode)
  }
  gdefun_handlers.each_with_index { |h, i|
    h.analyze
  }

  # create a GforHandler for each 'goose parallel for' pragma.
  #
  gfor_handlers = []
  gfors = rootnode.recursive_select(:gfor_stmt)
  gfors.each_with_index {|gfor, i|
    gfor_handlers[i] = Goose::GforHandler.new(gdefun_handlers, hw_spec[app_context[:arch]][:funcs])
    gfor_handlers[i].verbose = @verbose
    gfor_handlers[i].load(gfor)
  }
  gfor_handlers.each_with_index { |h, i|
    h.flatten(h.kernelnode)
  }
  gfor_handlers.each_with_index { |h, i|
    h.analyze
  }
#  rootnode.dump(0) if @verbose > 2

  # create filenames.
  #
  app_context[:infile] =~ / ([^\/]+) \. \w+ \z /x
  fnamebase = $1
#  fnamebase = File.basename(app_context[:infile], '.c')
  app_context[:fnamebase] = fnamebase
  app_context[:wfile] = "#{fnamebase}_#{app_context[:arch].to_s}.c"
  gfor_handlers.each_with_index { |h, i|
    app_context[:qfiles].push "#{fnamebase}_#{i}.q"
    app_context[:hfiles].push "#{fnamebase}_#{i}.vsmgen.h"
  }
  vinfo("Infile : #{app_context[:infile]}\n");
  vinfo("Wfile  : #{app_context[:wfile]}\n");
  vinfo("Qfile#{app_context[:qfiles].size == 1 ? ' ' : 's'} : " +
          "#{app_context[:qfiles].join(' ')}\n");
  vinfo("Hfile#{app_context[:hfiles].size == 1 ? ' ' : 's'} : " +
          "#{app_context[:hfiles].join(' ')}\n");

  # output Qfiles.
  #
  qgens = []
  gfor_handlers.each_with_index {|h, i|
    qgens[i] = Goose::QGenerator.new(h, gdefun_handlers, hw_spec[app_context[:arch]][:funcs])
    qgens[i].verbose = @verbose
    qfile = app_context[:qfiles][i] 
    open(qfile, "w") { |f|
      f.puts qgens[i].generate
    }
    open("#{qfile}.opt", "w") { |f|
      f << "precision: #{h.precision}\n"
      f << "asmfile: #{h.asmfile}\n" if h.asmfile
    }
  }

  # output a Wfile.
  #
  wg = Goose::WGenerator.new(gfor_handlers, qgens,
                             app_context[:infile], app_context[:prefix], app_context[:hfiles])
  wg.verbose = @verbose
  wfile = app_context[:wfile]

  %x{which indent >& /dev/null}
  if $? == 0
    open("| indent -i4 -sob -npcs -nut -br -bli0 > #{wfile}", "w") { |f|
      f.puts wg.generate
    }
  else
    vwarn("command 'indent' not found. Wfile (#{app_context[:wfile]}) may not nicely indented.\n");
    open("#{wfile}", "w") { |f|
      f.puts wg.generate
    }
  end

  if @verbose > 1
    gdefun_handlers.each_with_index { |h, i|
      puts "\nGoose func context#{i}:"
      h.diagnostics
    }
    gfor_handlers.each_with_index { |h, i|
      puts "\nGoose parallel for context#{i}:"
      h.diagnostics
    }
  end
  rootnode.dump(0) if @verbose > 3

end

toplevel
