#!/usr/bin/env ruby
require 'rubygems'
require "bundler/setup"
$:.unshift(File.expand_path("../../lib", __FILE__))
require 'rdf/turtle'
begin
  require 'linkeddata'
rescue LoadError
end
require 'logger'
require 'json/ld'
require 'getoptlong'
require 'open-uri'
require 'ruby-prof'

def run(input, options)
  if options[:profile]
    output_dir = File.expand_path("../../doc/profiles/#{File.basename __FILE__, ".rb"}", __FILE__)
    FileUtils.mkdir_p(output_dir)
    profile = RubyProf::Profile.new
    profile.exclude_methods!(Array, :each, :map)
    profile.exclude_method!(Hash, :each)
    profile.exclude_method!(Kernel, :require)
    profile.exclude_method!(Object, :run)
    profile.exclude_common_methods!
    profile.start
    run(input, **options.merge(profile: false))
    result = profile.stop
    
    # Print a graph profile to text
    printer = RubyProf::MultiPrinter.new(result)
    printer.print(path: output_dir, profile: "profile")
    puts "output saved in #{output_dir}"
    return
  end
  reader_class = RDF::Reader.for(options[:input_format].to_sym)
  raise "Reader not found for #{options[:input_format]}" unless reader_class

  start = Time.new
  if options[:flatten]
    output = JSON::LD::API.flatten(input, options.delete(:context), **options)
    secs = Time.new - start
    options[:output].puts output.to_json(JSON::LD::JSON_STATE)
    puts "Flattened in #{secs} seconds."
  elsif options[:expand]
    options = options.merge(expandContext: options.delete(:context)) if options.key?(:context)
    output = JSON::LD::API.expand(input, **options)
    secs = Time.new - start
    options[:output].puts output.to_json(JSON::LD::JSON_STATE)
    puts "Expanded in #{secs} seconds."
  elsif options[:compact]
    output = JSON::LD::API.compact(input, options[:context], **options)
    secs = Time.new - start
    options[:output].puts output.to_json(JSON::LD::JSON_STATE)
    puts "Compacted in #{secs} seconds."
  elsif options[:frame]
    output = JSON::LD::API.frame(input, options[:frame], **options)
    secs = Time.new - start
    options[:output].puts output.to_json(JSON::LD::JSON_STATE)
    puts "Framed in #{secs} seconds."
  else
    r = reader_class.new(input, **options[:parser_options])
    if options[:output_format] == :none
      num = 0
      r.each_statement { num += 1 } # Eat statements
      secs = Time.new - start
    else
      g = RDF::Repository.new << r
      secs = Time.new - start
      num = g.count
      parser_options = options[:parser_options].merge(prefixes: r.prefixes, standard_prefixes: true)
      options[:output].puts g.dump(options[:output_format], **parser_options)
      puts
    end
    puts "Parsed #{num} statements in #{secs} seconds @ #{num/secs} statements/second."
  end
rescue
  fname = input.respond_to?(:path) ? input.path : "-stdin-"
  STDERR.puts("Error in #{fname}")
  raise
end

logger = Logger.new(STDERR)
logger.level = Logger::WARN
logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"}

parser_options = {
  base:     nil,
  progress: false,
  profile:  false,
  validate: false,
  logger:   logger,
}

options = {
  parser_options: parser_options,
  output:        STDOUT,
  output_format: :turtle,
  input_format:  :jsonld,
  logger:        logger,
}
input = nil

OPT_ARGS = [
  ["--debug", GetoptLong::NO_ARGUMENT,                "Debug output"],
  ["--compact", GetoptLong::NO_ARGUMENT,              "Compact input, using context"],
  ["--context", GetoptLong::REQUIRED_ARGUMENT,        "Context used for compaction"],
  ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT,  "Use option argument as the patch input"],
  ["--expand", GetoptLong::NO_ARGUMENT,               "Expand input"],
  ["--expanded", GetoptLong::NO_ARGUMENT,             "Input is already expanded"],
  ["--flatten", GetoptLong::NO_ARGUMENT,              "Flatten input"],
  ["--format", GetoptLong::REQUIRED_ARGUMENT,         "Output format, for RDF output"],
  ["--frame", GetoptLong::REQUIRED_ARGUMENT,          "Frame input, option value is frame to use"],
  ["--help", "-?", GetoptLong::NO_ARGUMENT,           "This message"],
  ["--input-format", GetoptLong::REQUIRED_ARGUMENT,   "Format of input, if not JSON-LD"],
  ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,   "Where to store output (default STDOUT)"],
  ["--profile", GetoptLong::NO_ARGUMENT,              "Run profiler with output to doc/profiles/"],
  ["--quiet", GetoptLong::NO_ARGUMENT,                "Reduce output"],
  ["--rdfstar", GetoptLong::NO_ARGUMENT,              "RDF-star mode"],
  ["--stream", GetoptLong::NO_ARGUMENT,               "Streaming reader/writer"],
  ["--uri", GetoptLong::REQUIRED_ARGUMENT,            "Run with argument value as base"],
  ["--validate", GetoptLong::NO_ARGUMENT,             "Validate input"],
]

opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]})

def usage
  STDERR.puts %{Usage: #{$0} [options] file ...}
  width = OPT_ARGS.map do |o|
    l = o.first.length
    l += o[1].length + 2 if o[1].is_a?(String)
    l
  end.max
  OPT_ARGS.each do |o|
    s = "  %-*s  " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])]
    s += o.last
    STDERR.puts s
  end
  exit(1)
end

opts.each do |opt, arg|
  case opt
  when '--debug'        then logger.level = Logger::DEBUG
  when '--compact'      then options[:compact] = true
  when '--context'      then options[:context] = arg
  when '--execute'      then input = arg
  when '--expand'       then options[:expand] = true
  when '--expanded'     then options[:expanded] = true
  when '--flatten'      then options[:flatten] = true
  when '--format'       then options[:output_format] = arg.to_sym
  when '--frame'        then options[:frame] = arg
  when "--help"         then usage
  when '--input-format' then options[:input_format] = arg.to_sym
  when '--output'       then options[:output] = File.open(arg, "w")
  when '--profile'      then options[:profile] = true
  when '--quiet'
    options[:quiet] = true
    logger.level = Logger::FATAL
  when '--rdfstar'      then parser_options[:rdfstar] = true
  when '--stream'       then parser_options[:stream] = true
  when '--uri'          then parser_options[:base] = arg
  when '--validate'     then parser_options[:validate] = true
  end
end

if ARGV.empty?
  s = input ? input : $stdin.read
  run(StringIO.new(s), options)
else
  ARGV.each do |test_file|
    io = Kernel.open(test_file)
    io.define_singleton_method(:content_type) {"text/html"} if test_file.end_with?('.html')
    run(io, options)
  end
end
puts
