#!/usr/bin/env ruby

require "concurrent"
require "stepmod/utils/stepmod_file_annotator"
require "fileutils"

require "bundler/setup"
require "optparse"

def log(message, indent = 0)
  indent_spaces = " " * (indent * 2)
  puts "[stepmod-utils] #{indent_spaces}#{message}"
end

options = {}
OptionParser.new do |opts|
  opts.banner = "Usage: #{$0} [options]"

  opts.on(
    "--stepmod-dir STEPMOD_DIR",
    String,
    "Path to STEPmod directory",
  ) do |path|
    options[:stepmod_dir] = path
  end

  opts.on(
    "--schemas SRL_SCHEMAS_DIR",
    String,
    "Path to output SRL schemas",
  ) do |path|
    options[:srl_output_schemas_dir] = path
  end

  opts.on(
    "--documents SRL_DOCS_DIR",
    String,
    "Path to SRL documents (generated by stepmod2mn)",
  ) do |path|
    options[:srl_output_docs_dir] = path
  end

  opts.on(
    "--eqn-log LOG_FILE_DIR",
    String,
    "Path to the directory for log file for equations",
  ) do |path|
    options[:eqn_log_dir] = path
  end

  opts.on_tail("-h", "--help", "Show this message") do
    puts opts
    exit
  end
end.parse!

stepmod_dir = options[:stepmod_dir]
raise StandardError.new("--stepmod-dir not set") unless stepmod_dir

stepmod_path = Pathname.new(stepmod_dir)
unless (stepmod_path.join("data").exist? && stepmod_path.join("data").directory?)
  raise StandardError.new("--stepmod-dir should be the root STEPmod directory and contains data/")
end
log "STEPmod data path: `#{stepmod_dir}`"

srl_output_schemas_dir = options[:srl_output_schemas_dir]
raise StandardError.new("--schemas directory for SRL schemas not set") unless srl_output_schemas_dir
log "SRL schemas output path: `#{srl_output_schemas_dir}`"

srl_output_docs_dir = options[:srl_output_docs_dir]
raise StandardError.new("--documents directory for SRL documents (generated by stepmod2mn) not set") unless srl_output_docs_dir
log "SRL documents path: `#{srl_output_docs_dir}`"

unless File.exist?(srl_output_docs_dir) && File.directory?(srl_output_docs_dir)
  raise StandardError.new("--documents directory for SRL documents (generated by stepmod2mn) does not exist yet, please run stepmod2mn")
end

eqn_log_dir = File.expand_path(options[:eqn_log_dir] || ".")
Stepmod::Utils.eqn_log_dir = eqn_log_dir

def all_express_files(stepmod_dir)
  index_file = File.read(File.join(stepmod_dir, "repository_index.xml"))
  index = Nokogiri::XML(index_file).root

  files = []
  index.xpath("modules/module").each do |item|
    files << "#{stepmod_dir}/data/modules/#{item['name']}/arm.exp"
    files << "#{stepmod_dir}/data/modules/#{item['name']}/mim.exp"
  end

  index.xpath("resources/resource").each do |item|
    next if item["name"] == "iso13584_expressions_schema"

    files << "#{stepmod_dir}/data/resources/#{item['name']}/#{item['name']}.exp"
  end

  index.xpath("business_object_models/business_object_model").each do |item|
    files << "#{stepmod_dir}/data/business_object_models/#{item['name']}/bom.exp"
    files << "#{stepmod_dir}/data/business_object_models/#{item['name']}/DomainModel.exp"
  end

  files.filter { |file| File.exist?(file) }
end

# On MacOS File.exist? was behaving case sensitive while on
# Github Actions it was behaving as case in-sensitive, e.g
# File.exist?(`../resource_docs/geometric_and_topological_representation/RationalLRsurf.gif`)
# was returning true on MacOS and false on Github Action runner. Because
# the original filepath was `geometric_and_topological_representation/RationalLRSurf.gif` (notice the capital `S` for Surf.gif)
# So this method is to find the actual path of the file based on
# downcased name of the file,
#
# Example:
#
# Given `../resource_docs/geometric_and_topological_representation/RationalLRsurf.gif`
#   return `../resource_docs/geometric_and_topological_representation/RationalLRSurf.gif`
# It will return nil if the file does not exist
def file_system_path(filepath, filename_cache)
  dir = File.dirname(filepath)

  return filename_cache[dir][filepath.downcase] if filename_cache[dir]

  files = Dir.glob(File.join(dir, "*")).map { |f| [f.downcase, f] }.to_h
  filename_cache[dir] = files

  filename_cache[dir][filepath.downcase]
end

MAX_THREADS = 1 #[2, Concurrent.processor_count].max * 2
MAX_QUEUE_SIZE = MAX_THREADS * 4
# https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/thread_pools.md
# pool = Concurrent::ThreadPoolExecutor.new(
#   min_threads: MAX_THREADS,
#   max_threads: MAX_THREADS,
#   max_queue: MAX_QUEUE_SIZE,
#   fallback_policy: :caller_runs,
# )

files = all_express_files(stepmod_dir)
filename_cache = {}

FileUtils.mkdir_p(srl_output_schemas_dir)
srl_output_schemas_path = Pathname.new(srl_output_schemas_dir)
srl_output_docs_path = Pathname.new(srl_output_docs_dir)


files.each do |file|
  puts "#{Thread.current.object_id}: `#{file}` processing..."

  result = Stepmod::Utils::StepmodFileAnnotator.new(
    express_file: file,
    stepmod_dir: stepmod_dir,
  ).call

  destination_rel_path = Pathname.new(file).dirname.relative_path_from(stepmod_path.join("data"))
  directory_type = destination_rel_path.each_filename.to_a.first

  destination_path = srl_output_schemas_path.join(destination_rel_path)
  FileUtils.mkdir_p(destination_path)

  annotated_file_path = destination_path.join(File.basename(file))

  File.open(annotated_file_path, "w") do |annotated_file|
    annotated_file.puts(result[:annotated_text])
  end
  puts "#{Thread.current.object_id}: Written to: `#{annotated_file_path}`"

  result[:images_references].each do |source, destination|
    source_path = stepmod_path.join("data", source)
    system_source_path = file_system_path(source_path.to_s, filename_cache)
    image_dest_path = destination_path.join(destination)

    if image_dest_path.exist?
      puts "#{Thread.current.object_id}:     Copy image SKIPPED for `#{image_dest_path}` as destination exists."
      next
    end

    unless system_source_path
      source_file = Pathname.new(source).basename.to_s

      puts "#{Thread.current.object_id}:     Copy image NOT FOUND at schema path: `#{source_file}`, trying glob..."

      source_path = (srl_output_schemas_path.glob("**/#{source_file}") + srl_output_docs_path.glob("**/#{source_file}")).first
      if source_path
        puts "EXISTS" if source_path.exist?
        system_source_path = file_system_path(source_path.to_s, filename_cache)
        puts "#{Thread.current.object_id}:     Copy image FOUND at different path: `#{system_source_path}`"
      end
    end

    unless system_source_path
      puts "#{Thread.current.object_id}:     Copy image MISSING from `#{source_path}` to `#{image_dest_path}`"
      next
    end

    FileUtils.cp(system_source_path, image_dest_path)
    puts "#{Thread.current.object_id}:     Copy image SUCCESS: `#{system_source_path}` to `#{image_dest_path}`"
  end

  puts "#{Thread.current.object_id}: Done processing `#{File.basename(file)}` => `#{annotated_file_path}`"
end