#!/usr/bin/env ruby
require 'ruby2d/version'
require 'fileutils'

# Extending `String` to include some fancy colors
class String
  def colorize(c); "\e[#{c}m#{self}\e[0m" end
  def bold;  colorize('1')    end
  def error; colorize('1;31') end
end

# The installed gem directory
@gem_dir = "#{Gem::Specification.find_by_name('ruby2d').gem_dir}"

# The Ruby 2D library files
@lib_files = [
  'renderable',
  'exceptions',
  'color',
  'window',
  'application',
  'dsl',
  'quad',
  'line',
  'rectangle',
  'square',
  'triangle',
  'image',
  'sprite',
  'text',
  'sound',
  'music'
]

# Debugging command-line flag
@debug = false


# Check if source file provided is good
def check_build_src_file(rb_file)
  if !rb_file
    puts "Please provide a Ruby file to build"
    exit
  elsif !File.exists? rb_file
    puts "Can't find file: #{rb_file}"
    exit
  end
end


# Assemble the Ruby 2D library in one `.rb` file
def make_lib
  FileUtils.mkdir_p 'build'

  lib_dir = "#{@gem_dir}/lib/ruby2d/"

  lib = ""
  @lib_files.each do |f|
    lib << File.read("#{lib_dir + f}.rb") + "\n\n"
  end

  lib << "
include Ruby2D::DSL
include Ruby2D\n"

  File.write('build/lib.rb', lib)
end


# Remove `require 'ruby2d'` from source file
def strip_require(file)
  output = ''
  File.foreach(file) do |line|
    output << line unless line =~ /require ('|")ruby2d('|")/
  end
  return output
end


# Build a native version of the provided Ruby application
def build_native(rb_file)
  check_build_src_file(rb_file)

  # Check if MRuby exists; if not, quit
  if `which mruby`.empty?
    puts "#{'Error:'.error} Can't find MRuby, which is needed to build native Ruby 2D applications.\n"
    exit
  end

  # Assemble the Ruby 2D library in one `.rb` file and compile to bytecode
  make_lib
  `mrbc -Bruby2d_lib -obuild/lib.c build/lib.rb`

  # Read the provided Ruby source file, copy to build dir and compile to bytecode
  File.open('build/src.rb', 'w') { |file| file << strip_require(rb_file) }
  `mrbc -Bruby2d_app -obuild/src.c build/src.rb`

  # Combine contents of C source files and bytecode into one file
  open('build/app.c', 'w') do |f|
    f << "#define MRUBY 1" << "\n\n"
    f << File.read("build/lib.c") << "\n\n"
    f << File.read("build/src.c") << "\n\n"
    f << File.read("#{@gem_dir}/ext/ruby2d/ruby2d.c")
  end

  # Compile to a native executable
  `cc build/app.c -lmruby \`simple2d --libs\` -o build/app`

  # Clean up
  clean_up unless @debug

  # Success!
  puts "Native app created at `build/app`"
end


# Build a web-based version of the provided Ruby application
def build_web(rb_file)
  check_build_src_file(rb_file)

  # Assemble the Ruby 2D library in one `.rb` file and compile to JS
  make_lib
  `opal --compile --no-opal build/lib.rb > build/lib.js`

  # Read the provided Ruby source file, copy to build dir, and compile to JS
  File.open('build/src.rb', 'w') { |file| file << strip_require(rb_file) }
  `opal --compile --no-opal build/src.rb > build/src.js`
  FileUtils.cp "#{@gem_dir}/ext/ruby2d/ruby2d-opal.rb", "build/"
  `opal --compile --no-opal build/ruby2d-opal.rb > build/ruby2d-opal.js`

  # Combine contents of JS source files and compiled JS into one file
  open('build/app.js', 'w') do |f|
    f << File.read("#{@gem_dir}/assets/simple2d.js") << "\n\n"
    f << File.read("#{@gem_dir}/assets/opal.js") << "\n\n"
    f << File.read("build/lib.js") << "\n\n"
    f << File.read("build/ruby2d-opal.js") << "\n\n"
    f << File.read("build/src.js") << "\n\n"
  end

  # Copy over HTML template
  FileUtils.cp "#{@gem_dir}/assets/template.html", "build/app.html"

  # Clean up
  clean_up unless @debug

  # Success!
  puts "Web app created at `build/app.js`",
       "  Run by opening `build/app.html`"
end


# Build an iOS or tvOS app
def build_apple(rb_file, device)
  check_build_src_file(rb_file)

  # Check for Simple 2D framework,
  unless File.exists?('/usr/local/Frameworks/Simple2D/iOS/Simple2D.framework') && File.exists?('/usr/local/Frameworks/Simple2D/tvOS/Simple2D.framework')
    puts "#{'Error:'.error} Simple 2D iOS and tvOS frameworks not found. Install them and try again.\n"
    exit
  end

  # Check if MRuby exists; if not, quit
  if `which mruby`.empty?
    puts "#{'Error:'.error} Can't find MRuby, which is needed to build native Ruby 2D applications.\n"
    exit
  end

  # Assemble the Ruby 2D library in one `.rb` file and compile to bytecode
  make_lib
  `mrbc -Bruby2d_lib -obuild/lib.c build/lib.rb`

  # Read the provided Ruby source file, copy to build dir and compile to bytecode
  File.open('build/src.rb', 'w') { |file| file << strip_require(rb_file) }
  `mrbc -Bruby2d_app -obuild/src.c build/src.rb`

  # Copy over iOS project
  FileUtils.cp_r "#{@gem_dir}/assets/#{device}", "build"

  # Combine contents of C source files and bytecode into one file
  File.open("build/#{device}/main.c", 'w') do |f|
    f << "#define RUBY2D_IOS_TVOS 1" << "\n\n"
    f << "#define MRUBY 1" << "\n\n"
    f << File.read("build/lib.c") << "\n\n"
    f << File.read("build/src.c") << "\n\n"
    f << File.read("#{@gem_dir}/ext/ruby2d/ruby2d.c")
  end

  # Build the Xcode project
  `simple2d build --#{device} build/#{device}/MyApp.xcodeproj`

  # Clean up
  clean_up unless @debug

  # Success!
  puts "App created at `build/#{device}`"
end


# Clean up unneeded build files
def clean_up(cmd = nil)
  FileUtils.rm(
    Dir.glob('build/{src,lib}.{rb,c,js}') +
    Dir.glob('build/ruby2d-opal.{rb,js}') +
    Dir.glob('build/app.c')
  )
  if cmd == :all
    puts "cleaning up..."
    FileUtils.rm_f 'build/app'
    FileUtils.rm_f 'build/app.js'
    FileUtils.rm_f 'build/app.html'
    FileUtils.rm_rf 'build/ios'
    FileUtils.rm_rf 'build/tvos'
  end
end


# Launch a native app
def launch_native
  if !File.exists? 'build/app'
    puts "No native app built!"
    exit
  end
  `( cd build && ./app )`
end


# Launch a web app
def launch_web
  if !File.exists? 'build/app.html'
    puts "No web app built!"
    exit
  end
  open_cmd = 'open'
  case RUBY_PLATFORM
  when /linux/
    open_cmd = "xdg-#{open_cmd}"
  when /mingw/
    open_cmd = "start"
  end
  system "#{open_cmd} build/app.html"
end


# Launch an iOS or tvOS app in a simulator
def launch_apple(device)
  case device
  when 'ios'
    if !File.exists? 'build/ios/build/Release-iphonesimulator/MyApp.app'
      puts "No iOS app built!"
      exit
    end
    puts `simple2d simulator --open "iPhone 8" &&
          simple2d simulator --install "build/ios/build/Release-iphonesimulator/MyApp.app" &&
          simple2d simulator --launch "Ruby2D.MyApp"`
  when 'tvos'
    if !File.exists? 'build/tvos/build/Release-appletvsimulator/MyApp.app'
      puts "No tvOS app built!"
      exit
    end
    puts `simple2d simulator --open "Apple TV" &&
          simple2d simulator --install "build/tvos/build/Release-appletvsimulator/MyApp.app" &&
          simple2d simulator --launch "Ruby2D.MyApp"`
  end
end


# Check Command-line Arguments #################################################

usage = "Ruby 2D: Make cross-platform 2D applications in Ruby".bold + "\n
Usage: ruby2d <command> <options>
              [-v|--version]

Summary of commands and options:
  build         Build a Ruby source file
  launch        Launch a built Ruby 2D application
  simulator     Interact with iOS and tvOS simulators
  -v|--version  Prints the installed version\n\n"

usage_build = "
Use the #{"build".bold} command to compile or build Ruby 2D apps.

To compile and create a native executable, use:
  build --native <ruby_file>

To build for the web, creating a JavaScript and HTML package, use:
  build --web <ruby_file>

To build an iOS or tvOS app, use the following:
  build --ios <ruby_file>
  build --tvos <ruby_file>

To build for every platform supported by the current system, use:
  build --all <ruby_file>

To clean up the build directory, use:
  build --clean

Add the #{"--debug".bold} option to print debugging info
and keep all intermediate files generated.\n\n"

usage_launch = "
Use the #{"launch".bold} command to run a Ruby 2D app that has been built.
Choose one of the following options to select the kind of app to run:

  --native
  --web
  --ios
  --tvos\n\n"

usage_simulator = "
Choose an option with the #{"simulator".bold} command:

  --list      List available devices
  --booted    Show currently booted devices

  --open <device_name>    Open a simulator device with a given device name

  --install <app_file>    Install an app on the booted simulator given the path
                          to the app e.g. \"Release-iphonesimulator/MyApp.app\"

  --launch <bundle_id>    Launch an app given the app bundle's identifier,
                          e.g. \"Ruby2D.MyApp\"

  --log           Stream log of the booted simulator
  --log <app>     Stream log for the app only, given the app name
  --log-errors    Stream log containing only error messages\n\n"

# puts ARGV.inspect

case ARGV[0]
when 'build'
  if ARGV.delete '--debug' then @debug = true end
  case ARGV[1]
  when '--native'
    build_native(ARGV[2])
  when '--web'
    build_web(ARGV[2])
  when '--ios'
    build_apple(ARGV[2], 'ios')
  when '--tvos'
    build_apple(ARGV[2], 'tvos')
  when '--all'
    build_native(ARGV[2])
    build_web(ARGV[2])
    build_apple(ARGV[2], 'ios')
    build_apple(ARGV[2], 'tvos')
  when '--clean'
    clean_up(:all)
  else
    puts usage_build
  end
when 'launch'
  case ARGV[1]
  when '--native'
    launch_native
  when '--web'
    launch_web
  when '--ios'
    launch_apple('ios')
  when '--tvos'
    launch_apple('tvos')
  else
    puts usage_launch
  end
when 'simulator'
  case ARGV[1]
  when '--list'
    puts `simple2d simulator --list`
  when '--booted'
    puts `simple2d simulator --booted`
  when '--open'
    puts `simple2d simulator --open "#{ARGV[2]}"`
  when '--install'
    puts `simple2d simulator --install "#{ARGV[2]}"`
  when '--launch'
    puts `simple2d simulator --launch "#{ARGV[2]}"`
  when '--log'
    puts `simple2d simulator --log`
  when '--log-errors'
    puts `simple2d simulator --log-errors`
  else
    puts usage_simulator
  end
when '-v', '--version'
  puts Ruby2D::VERSION
else
  puts usage
end
