require 'pathname'
require 'xcodeproj'

class Frankifier
  include Thor::Shell

  def self.frankify! root_dir, options = {}
    me = new(root_dir, options)
    me.frankify!
    me
  end

  def initialize( root_dir, options = {} )
    @root = Pathname.new( root_dir )
    @target_build_configuration = options[:build_config]
    @target_selection = options[:target]
    load_xcode_proj_option(options[:project])
  end

  def frankify!
    decide_on_project if @project.nil?
    decide_on_target
    report_project_and_target

    check_target_build_configuration_is_valid!

    say ''
    project_changed |= add_linker_flag

    say ''
    project_changed |= add_library_search_path

    if project_changed
      save_changes
    end
  end

  private
  def load_xcode_proj_option(xcodeproj)
    if xcodeproj
      unless File.exists?(xcodeproj)
        raise "Project file '#{xcodeproj}' does not exist. Please specify the relative path."
      end

      @xcodeproj_path = Pathname.new(xcodeproj)
      @project = Xcodeproj::Project.open(@xcodeproj_path)
    end
  end

  def decide_on_project
    projects = Pathname.glob( @root+'*.xcodeproj' )
    xcodeproj = case projects.size
    when 0
      raise "There are no .xcodeproj files in this directory. Please move to your root project directory and try again."
    when 1
      projects.first
    else
      choice = ask_menu(
        "I found more than one .xcodeproj. Which is the main app project that you wish to Frankify?",
        projects.map(&:basename)
      )
      projects[choice]
    end

    @xcodeproj_path = xcodeproj
    @project = Xcodeproj::Project.open(xcodeproj)
  end

  def decide_on_target
    targets = @project.targets
    @target = targets.find { |item| item.name.eql?(@target_selection) } if @target_selection
    return @target if @target
    @target = case targets.size
    when 0
      raise "Sorry, this project appears to contain no targets. Nothing I can do here."
    when 1
      targets.first
    else
      choice = ask_menu(
        "I found more than one target in this project. Which is the main app target that you wish to Frankify?",
        targets.map(&:name)
      )
      targets.to_a[choice]
    end
  end

  def report_project_and_target
    puts "Frankifying target [#{@target.name}] in project #{@xcodeproj_path.basename}"
  end

  def add_linker_flag
    add_frank_entry_to_build_setting( 'OTHER_LDFLAGS', 'FRANK_LDFLAGS' )
  end

  def add_library_search_path
    add_frank_entry_to_build_setting( 'LIBRARY_SEARCH_PATHS', 'FRANK_LIBRARY_SEARCH_PATHS' )
  end

  def add_frank_entry_to_build_setting( build_setting, entry_to_add )
    setting_array = Array( build_settings_to_edit[build_setting] )

    if setting_array.find{ |flag| flag.start_with? "$(FRANK_" }
      say "It appears that your '#{@target_build_configuration}' configuration's #{build_setting} build setting already include some FRANK setup. Namely: #{setting_array.inspect}. I won't change anything here."
      return false
    end

    say "Adding $(inherited) and $(#{entry_to_add}) to your '#{@target_build_configuration}' configuration's #{build_setting} build setting ..."
    setting_array.unshift "$(inherited)"
    setting_array << "$(#{entry_to_add})"
    setting_array.uniq! # mainly to avoid duplicate $(inherited) entries
    say "... #{build_setting} is now: #{setting_array.inspect}"

    build_settings_to_edit[build_setting] = setting_array
    true
  end

  def check_target_build_configuration_is_valid!
    unless @target.build_configuration_list.build_settings(@target_build_configuration)
      say %Q|I'm trying to Frankify the '#{@target_build_configuration}' build configuration, but I don't see it that build configuration in your XCode target. Here's a list of the build configurations I see:|
      @target.build_configuration_list.build_configurations.each do |bc|
        say "  '#{bc.name}'"
      end
      say ''
      say %Q|Please specify one of those build configurations using the --build_configuration flag|
      exit
    end

  end

  def build_settings_to_edit
    @_build_settings_to_edit ||= @target.build_configuration_list.build_settings(@target_build_configuration)
  end

  def save_changes
    @project.save
  end

  # TODO: send this as a pull request to thor
  def ask_menu message, options
    option_lines = options.each_with_index.map{ |o,i| "#{i+1}: #{o}" }
    full_message = ([message,'']+option_lines+[">"]).join("\n")

    allowed_range = (1..options.length)
    valid_choice = nil
    until valid_choice
      choice = ask(full_message).to_i
      valid_choice = choice if allowed_range.include?(choice)
      say("You must choose an option between #{allowed_range.first} and #{allowed_range.last}") unless valid_choice
    end
    valid_choice-1
  end

end