#!/usr/bin/ruby

require 'getoptlong'

require 'voodoo'
require 'voodoo/generators/dummy_generator'

USAGE="USAGE: voodooc [options] <input file>"
HELP=<<EOT
Input file denotes the file to compile. The special name '-' causes
voodooc to read from standard input. In that case, the -o option is
mandatory.

Valid options are:

-a <architecture>
--arch <architecture>
--architecture <architecture>
        Select target architecture. Use -a help to get a list of
        supported architectures.

-c
--check
        Check program for correctness, but do not actually compile it.

-f <format>
--format <format>
--output-format <format>
        Select output format. Use -a <architecture> -f help to get a list of
        supported output formats for architecture.

--features
        List the features supported by this implementation. The features
        are listed in the format <feature name><tab><version>, ordered
        by feature name. The program exits after printing the list of
        features; no compilation is performed.

-h
--help
        Display usage information and exit. No compilation will be performed.

-o <output file>
--output <output file>
--output-file <output file>
        Set output file name. The special name '-' causes voodooc to write
        to standard output.

--version
        Display version information and exit. No compilation will be performed.
EOT

architecture = Voodoo::Config.default_architecture
check_only = false
input_file = nil
output_file = nil
output_format = Voodoo::Config.default_format
show_features = false

# Gets the input file name from the command line.
def get_input_file_name
  if ARGV.length == 1
    input_file = ARGV[0]
  else
    if ARGV.length < 1
      $stderr.puts "no input files"
    else
      $stderr.puts "Too many arguments"
    end
    $stderr.puts USAGE
    exit 0x80
  end
  input_file
end

# If error is a Voodoo::Compiler::Error, prints error messages.
# Else, re-raises error.
def handle_error error
  # If error is a compiler error, iterate over all child errors and
  # print user-friendly messages. Else, re-raise so that the default
  # handling is performed.
  if error.kind_of? Voodoo::Compiler::Error
    error.errors.each do |e|
      message = ''
      if e.respond_to?(:start_line) && e.start_line != nil
        message << "#{e.start_line}: "
        if e.input_name != nil
          message = "#{e.input_name}:#{message}"
        else
          message = "line #{message}"
        end
      end
      message << e.message
      $stderr.puts message
      if e.respond_to?(:text) && e.text != nil
        $stderr.print "\n  #{e.text.gsub("\n", "\n  ")}\n"
      end
    end
  else
    raise error
  end
end

#
# Process command line
#

opts = GetoptLong.new(
  ['--arch', '--architecture', '-a', GetoptLong::REQUIRED_ARGUMENT],
  ['--check', '-c', GetoptLong::NO_ARGUMENT],
  ['--features', GetoptLong::NO_ARGUMENT],
  ['--help', '-h', GetoptLong::NO_ARGUMENT],
  ['--output-file', '--output', '-o', GetoptLong::REQUIRED_ARGUMENT],
  ['--output-format', '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ],
  ['--version', GetoptLong::NO_ARGUMENT]
)

# Process options
opts.each do |opt, arg|
  case opt
  when '--arch'
    architecture = arg.to_sym
  when '--check'
    check_only = true
  when '--features'
    show_features = true
  when '--help'
    puts USAGE
    puts HELP
    exit
  when '--output-file'
    output_file = arg
  when '--output-format'
    output_format = arg.to_sym
  when '--version'
    puts "#{Voodoo::Config::IMPLEMENTATION_NAME} version " +
      Voodoo::Config::IMPLEMENTATION_VERSION
    exit
  end
end

if architecture == :help
  # List supported architectures
  puts "Supported architectures:"
  puts Voodoo::CodeGenerator.architectures.map{|arch| arch.to_s}.sort
  exit
end

if output_format == :help
  # List supported output formats
  puts "Supported output formats for architecture #{architecture}:"
  puts Voodoo::CodeGenerator.formats(architecture).map{|f| f.to_s}.sort
  exit
end

if check_only
  input_file = get_input_file_name
  infile = input_file == '-' ? $stdin : open(input_file)
  begin
    parser = Voodoo::Parser.new infile
    generator = Voodoo::DummyGenerator.new
    compiler = Voodoo::Compiler.new parser, generator, nil
    compiler.compile
  rescue => error
    handle_error error
    exit 1
  ensure
    infile.close
  end
  puts 'OK'
  exit 0
end

# Select code generator based on output format
begin
  generator = Voodoo::CodeGenerator.get_generator :architecture => architecture,
                                                  :format => output_format
rescue NotImplementedError
  if Voodoo::CodeGenerator.architecture_supported? architecture
    $stderr.puts "Format #{output_format} is not supported for architecture #{architecture}"
    $stderr.puts "Supported formats for #{architecture} are:"
    $stderr.puts Voodoo::CodeGenerator.formats(architecture)
  else
    $stderr.puts "Architecture #{architecture} is not supported."
    $stderr.puts "Supported architectures are:"
    $stderr.puts Voodoo::CodeGenerator.architectures
  end
  exit 1
end

# If requested, show features and exit
if show_features
  generator.features.to_a.sort{|x,y| x[0].to_s <=> y[0].to_s}.each do |feature|
    puts "#{feature[0]}\t#{feature[1]}"
  end
  exit
end

input_file = get_input_file_name

#
# Compile
#

if input_file == '-'
  infile = $stdin
  unless output_file
    $stderr.puts "The -o option is mandatory when reading from standard input"
    exit 0x80
  end
else
  infile = open input_file
  # Set output_file if not already set
  output_file = generator.output_file_name input_file unless output_file
end

if output_file == '-'
  outfile = $stdout
else
  outfile = open output_file, 'w'
end

begin
  parser = Voodoo::Parser.new infile
  compiler = Voodoo::Compiler.new parser, generator, outfile

  compiler.compile
rescue => e
  if output_file != '-'
    File::unlink(output_file) if File::exists?(output_file)
  end

  handle_error e
  exit 1
ensure
  outfile.close
  infile.close
end
