#!/usr/bin/ruby
# Copyright (C) 2012, HipByte SPRL. All Rights Reserved.
#
# This file is subject to the terms and conditions of the End User License
# Agreement accompanying the package this file is a part of.

require 'fileutils'
require 'uri'

relative_libdir = File.expand_path(File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), '../lib'))
$motion_libdir = File.exist?(File.join(relative_libdir, 'motion')) ? relative_libdir : '/Library/RubyMotion/lib'
$:.unshift($motion_libdir)
require 'motion/version'
require 'motion/project/app.rb'
require 'motion/project/builder.rb'

class Command
  class << self
    attr_accessor :name
    attr_accessor :help
  
    def die(*msg)
      $stderr.puts msg
      exit 1
    end

    def need_root
      if Process.uid != 0
        die "You need to be root to run this command."
      end
    end

    def read_license_key
      unless File.exist?(LicensePath)
        die "License file not present. Please activate RubyMotion with `motion activate' and try again."
      end
      File.read(LicensePath).strip
    end

    def guess_email_address
      # Guess the default email address from git.
      URI.escape(`git config --get user.email`.strip)
    end
  end
end

class CreateCommand < Command
  self.name = 'create'
  self.help = 'Create a new project'

  def self.run(args)
    if args.size < 1 || args.size > 2
    end

    app_name = nil
    template_name = 'ios'
    args.each do |a|
      case a
        when /--([^=]+)=(.+)/
          opt_name = $1.to_s.strip
          opt_val = $2.to_s.strip
          case opt_name
            when 'template'
              template_name = opt_val
            else
              die "Incorrect option `#{opt_name}'"
          end
        else
          if app_name
            app_name = nil
            break
          else
            app_name = a
          end
      end
    end

    unless app_name
      die "Usage: motion create [--template=<template_name>] <app-name>"
    end

    Motion::Project::App.create(app_name, template_name)
  end
end

LicensePath = '/Library/RubyMotion/license.key'

class ActivateCommand < Command
  self.name = 'activate'
  self.help = 'Activate the software license'

  def self.run(args)
    if File.exist?(LicensePath)
      die "Product is already activated. Delete the license file `#{LicensePath}' if you want to activate a new license."
    end

    if args.size != 1
      die "Usage: motion activate <license-key>"
    end

    license_key = args[0]
    unless license_key.match(/^[a-f0-9]{40}$/)
      die "Given license key `#{license_key}' seems invalid. It should be a string of 40 hexadecimal characters. Check the mail you received after the order, or contact us if you need any help: info@hipbyte.com"
    end

    need_root
    File.open(LicensePath, 'w') { |io| io.write license_key }
    puts "Product activated. Thanks for purchasing RubyMotion :-)"
  end
end

class AccountCommand < Command
  self.name = 'account'
  self.help = 'Access the software license account'

  def self.run(args)
    unless args.empty?
      die "Usage: motion account"
    end

    license_key = read_license_key
    email = guess_email_address

    system("open \"https://secure.rubymotion.com/account?license_key=#{license_key}&email=#{email}\"")
  end
end

class SupportCommand < Command
  self.name = 'support'
  self.help = 'Create a support ticket'

  def self.run(args)
    unless args.empty?
      die "Usage: motion support"
    end

    license_key = read_license_key
    email = guess_email_address

    # Collect details about the environment.
    osx_vers = `/usr/bin/sw_vers -productVersion`.strip
    rm_vers = Motion::Version
    xcode_vers = begin
      xcodebuild = `which xcodebuild`.strip
      xcodebuild = '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild' if xcodebuild.empty?
      vers = ''
      if File.exist?(xcodebuild)
        vers = `#{xcodebuild} -version`.strip.scan(/Xcode\s(.+)$/).flatten[0].to_s
      end
      vers = 'unknown' if vers.empty?
      vers
    end

    environment = URI.escape("OSX #{osx_vers}, RubyMotion #{rm_vers}, Xcode #{xcode_vers}")

    system("open \"https://secure.rubymotion.com/new_support_ticket?license_key=#{license_key}&email=#{email}&environment=#{environment}\"")
  end
end

class UpdateCommand < Command
  self.name = 'update'
  self.help = 'Update the software'

  def self.curl(cmd)
    resp = `/usr/bin/curl --connect-timeout 60 #{cmd}`
    if $?.exitstatus != 0
      die "Error when connecting to the server. Check your Internet connection and try again."
    end
    resp
  end

  def self.run(args)
    check_mode = false
    wanted_software_version = ''
    args.each do |a|
      case a
        when '--check'
          check_mode = true
        when /--force-version=(.+)/
          wanted_software_version = $1.to_s
        else
          die "Usage: motion update [--force-version=X]"
      end
    end

    product_version = Motion::Version
    if check_mode
      update_check_file = File.join(ENV['TMPDIR'] || '/tmp', '.motion-update-check')
      if !File.exist?(update_check_file) or (Time.now - File.mtime(update_check_file) > 60 * 60 * 24)
        resp = curl("-s -d \"product=rubymotion\" -d \"current_software_version=#{product_version}\" http://secure.rubymotion.com/latest_software_version")
        exit 1 unless resp.match(/^\d+\.\d+/)
        File.open(update_check_file, 'w') { |io| io.write(resp) }
      end
      exit File.read(update_check_file) > product_version ? 2 : 0
    end

    unless File.exist?(LicensePath)
      die "License file not present. Please activate RubyMotion with `motion activate' and try again."
    end

    need_root
    license_key = File.read(LicensePath).strip

    $stderr.puts "Connecting to the server..."
    resp = curl("-s -d \"product=rubymotion\" -d \"current_software_version=#{product_version}\" -d \"wanted_software_version=#{wanted_software_version}\" -d \"license_key=#{license_key}\" https://secure.rubymotion.com/update_software")
    unless resp.match(/^http:/)
      die resp
    end

    $stderr.puts "Downloading software update..."
    url = resp
    tmp_dest = '/tmp/_rubymotion_su.pkg'
    curl("-# \"#{url}\" -o #{tmp_dest}")

    $stderr.puts "Installing software update..."
    installer = "/usr/sbin/installer -pkg \"#{tmp_dest}\" -target / >& /tmp/installer.stderr"
    unless system(installer)
      die "An error happened when installing the software update: #{File.read('/tmp/installer.stderr')}"
    end
    FileUtils.rm_f tmp_dest

    $stderr.puts "Software update installed.\n\n"
    news = File.read('/Library/RubyMotion/NEWS')
    news.lines.each do |line|
      if md = line.match(/^=\s+RubyMotion\s+(.+)\s+=$/)
        break if md[1] <= product_version
      end
      $stderr.puts line
    end
    $stderr.puts "(See the `/Library/RubyMotion/NEWS' file for all changes.)"

    FileUtils.rm_rf Motion::Project::Builder.common_build_dir
  end
end

class RICommand < Command
  self.name = 'ri'
  self.help = 'Display API reference'

  def self.run(args)
    if args.size <= 0
      die "Usage: motion ri <API-name>"
    end

    system("/Library/RubyMotion/lib/yard/bin/yri --db /Library/RubyMotion/doc/yardoc #{args[0]}")
  end
end

class MotionMainCommand
  Commands = [CreateCommand, ActivateCommand, UpdateCommand, RICommand, SupportCommand, AccountCommand]

  def initialize(args)
    arg = args.shift
    case arg
      when '-h', '--help'
        usage
      when '-v', '--version'
        $stdout.puts Motion::Version
        exit 1
      when /^-/
        $stderr.puts "Unknown option: #{arg}"
        exit 1
    end
    command = Commands.find { |command| command.name == arg }
    usage unless command
    command.run(args)
  end

  def usage
    $stderr.puts 'Usage:'
    $stderr.puts "  #{File.basename(__FILE__)} [-h, --help]"
    $stderr.puts "  #{File.basename(__FILE__)} [-v, --version]"
    $stderr.puts "  #{File.basename(__FILE__)} <command> [<args...>]"
    $stderr.puts ''
    $stderr.puts 'Commands:'
    Commands.each do |command|
      $stderr.puts "  #{command.name}".ljust(15) + command.help
    end
    exit 1
  end
end

MotionMainCommand.new(ARGV)
