#!/usr/bin/env ruby

#these are always used
require 'rubygems'
require 'fileutils'

# Check for the main project file (either the one defined in the ENV or the default)
main_filepath = ENV['CEEDLING_MAIN_PROJECT_FILE']
project_found = (!main_filepath.nil? && File.exists?(main_filepath))
if (!project_found)
  main_filepath = "project.yml"
  project_found = File.exists?(main_filepath)
end

def is_windows?
  return ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false) if defined?(RbConfig)
  return ((Config::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false)
end

unless (project_found)
#===================================== We Do Not Have A Project ================================================

  puts "Welcome to Ceedling!"
  require 'thor'

  def here
    File.dirname(__FILE__) + "/.."
  end

  class CeedlingTasks < Thor
    include Thor::Actions

    desc "new PROJECT_NAME", "create a new ceedling project"
    method_option :no_docs, :type => :boolean, :default => false, :desc => "No docs in vendor directory"
    method_option :nodocs, :type => :boolean, :default => false
    method_option :as_gem, :type => :boolean, :default => false, :desc => "Create the scaffold using Ceedling as a gem instead of filling in the vendor directory. Implies --no-docs."
    method_option :asgem, :type => :boolean, :default => false
    method_option :with_ignore, :type => :boolean, :default => false, :desc => "Create a gitignore file for ignoring ceedling generated files."
    method_option :withignore, :type => :boolean, :default => false
    method_option :no_configs, :type => :boolean, :default => false, :desc => "Don't install starter configuration files."
    method_option :noconfigs, :type => :boolean, :default => false
    def new(name, silent = false)
      copy_assets_and_create_structure(name, silent, false, options)
    end

    desc "upgrade PROJECT_NAME", "upgrade ceedling for a project (not req'd if gem used)"
    method_option :no_docs, :type => :boolean, :default => false, :desc => "No docs in vendor directory"
    method_option :nodocs, :type => :boolean, :default => false
    method_option :no_configs, :type => :boolean, :default => true, :desc => "Don't install starter configuration files."
    method_option :noconfigs, :type => :boolean, :default => false
    def upgrade(name, silent = false)
      copy_assets_and_create_structure(name, silent, true, options)
    end

    no_commands do
      def copy_assets_and_create_structure(name, silent=false, force=false, options = {})

        no_docs     = options[:no_docs]     || options[:nodocs]     || false
        no_configs  = options[:no_configs]  || options[:noconfigs]  || false
        as_gem      = options[:as_gem]      || options[:asgem]      || false
        with_ignore = options[:with_ignore] || options[:withignore] || false

        ceedling_path     = File.join(name, 'vendor', 'ceedling')
        source_path       = File.join(name, 'src')
        test_path         = File.join(name, 'test')
        test_support_path = File.join(name, 'test/support')

        [source_path, test_path, test_support_path].each do |d|
          FileUtils.mkdir_p d
        end

        unless as_gem
          FileUtils.mkdir_p ceedling_path

          unless no_docs
            doc_path = File.join(ceedling_path, 'docs')
            FileUtils.mkdir_p doc_path

            in_doc_path = lambda {|f| File.join(doc_path, f)}

            doc_files = [
                          'docs/CeedlingPacket.md',
                          'vendor/c_exception/docs/CException.md',
                          'vendor/cmock/docs/CMock_Summary.md',
                          'vendor/unity/docs/UnityAssertionsCheatSheetSuitableforPrintingandPossiblyFraming.pdf',
                          'vendor/unity/docs/UnityAssertionsReference.md',
                          'vendor/unity/docs/UnityConfigurationGuide.md',
                          'vendor/unity/docs/UnityGettingStartedGuide.md',
                          'vendor/unity/docs/UnityHelperScriptsGuide.md',
                          'vendor/unity/docs/ThrowTheSwitchCodingStandard.md',
                        ]

            doc_files.each do |f|
              copy_file(f, in_doc_path.call(File.basename(f)), :force => force)
            end
          end

          folders = if as_gem
            %w{plugins lib}
          else
            %w{plugins lib bin}
          end

          #copy full folders from ceedling gem into project
          folders.map do |f|
            {:src => f, :dst => File.join(ceedling_path, f)}
          end.each do |f|
            directory(f[:src], f[:dst], :force => force)
          end

          #copy necessary subcomponents from ceedling gem into project
          sub_components = [
            {:src => 'vendor/c_exception/lib/',     :dst => 'vendor/c_exception/lib'},
            {:src => 'vendor/c_exception/release/', :dst => 'vendor/c_exception/release'},
            {:src => 'vendor/cmock/config/',        :dst => 'vendor/cmock/config'},
            {:src => 'vendor/cmock/lib/',           :dst => 'vendor/cmock/lib'},
            {:src => 'vendor/cmock/release/',       :dst => 'vendor/cmock/release'},
            {:src => 'vendor/cmock/src/',           :dst => 'vendor/cmock/src'},
            {:src => 'vendor/deep_merge/lib/',      :dst => 'vendor/deep_merge/lib'},
            {:src => 'vendor/diy/lib',              :dst => 'vendor/diy/lib'},
            {:src => 'vendor/unity/auto/',          :dst => 'vendor/unity/auto'},
            {:src => 'vendor/unity/release/',       :dst => 'vendor/unity/release'},
            {:src => 'vendor/unity/src/',           :dst => 'vendor/unity/src'},
          ]

          sub_components.each do |c|
            directory(c[:src], File.join(ceedling_path, c[:dst]), :force => force)
          end
        end

        unless (no_configs)
          if as_gem
            copy_file(File.join('assets', 'project_as_gem.yml'), File.join(name, 'project.yml'), :force => force)
          else
            copy_file(File.join('assets', 'project_with_guts.yml'), File.join(name, 'project.yml'), :force => force)
            if is_windows?
              copy_file(File.join('assets', 'ceedling.cmd'), File.join(name, 'ceedling.cmd'), :force => force)
            else
              copy_file(File.join('assets', 'ceedling'), File.join(name, 'ceedling'), :force => force)
            end
          end
        end

        if (with_ignore)
          copy_file(File.join('assets', 'default_gitignore'), File.join(name, '.gitignore'), :force => force)
        end

        unless silent
          puts "\n"
          puts "Project '#{name}' #{force ? "upgraded" : "created"}!"
          puts " - Tool documentation is located in vendor/ceedling/docs" if (not no_docs) and (not as_gem)
          puts " - Execute 'ceedling help' to view available test & build tasks"
          puts ''
        end
      end
    end

    desc "examples", "list available example projects"
    def examples()
      puts "Available sample projects:"
      FileUtils.cd(File.join(here, "examples")) do
        Dir["*"].each {|proj| puts "  #{proj}"}
      end
    end

    desc "example PROJ_NAME [DEST]", "new specified example project (in DEST, if specified)"
    def example(proj_name, dest=nil)
      if dest.nil? then dest = proj_name end

      invoke :new, [dest, true]

      dest_src      = File.join(dest,'src')
      dest_test     = File.join(dest,'test')
      dest_project  = File.join(dest,'project.yml')

      directory "examples/#{proj_name}/src",         dest_src
      directory "examples/#{proj_name}/test",        dest_test
      remove_file dest_project
      copy_file "examples/#{proj_name}/project.yml", dest_project

      puts "\n"
      puts "Example project '#{proj_name}' created!"
      puts " - Tool documentation is located in vendor/ceedling/docs"
      puts " - Execute 'ceedling help' to view available test & build tasks"
      puts ''
    end

    desc "version", "return the version of the tools installed"
    def version()
      require 'ceedling/version.rb'
      puts "  Ceedling::   #{Ceedling::Version::CEEDLING}"
      puts "  CMock::      #{Ceedling::Version::CMOCK}"
      puts "  Unity::      #{Ceedling::Version::UNITY}"
      puts "  CException:: #{Ceedling::Version::CEXCEPTION}"
    end
  end

  if (ARGV[0] =~ /^\-T$/)
    puts "\n(No Project Detected, Therefore Showing Options to Create Projects)"
    CeedlingTasks.tasks.each_pair do |k,v|
      puts v.usage.ljust(25,' ') + v.description
    end
    puts "\n"
  else
    CeedlingTasks.source_root here
    CeedlingTasks.start
  end

#===================================== We Have A Project Already ================================================
else
  require 'yaml'
  require 'rbconfig'

  #determine platform
  platform = begin
    case(RbConfig::CONFIG['host_os'])
      when /mswin|mingw|cygwin/i
        :mswin
      when /darwin/
        :osx
      else
        :linux
    end
  rescue
    :linux
  end

  #create our default meta-runner option set
  options = {
    :pretest => nil,
    :args => [],
    :add_path => [],
    :path_connector => (platform == :mswin) ? ";" : ":",
    :graceful_fail => false,
    :which_ceedling => (Dir.exists?("vendor/ceedling") ? "vendor/ceedling" : 'gem'),
    :default_tasks => [ 'test:all' ],
    :list_tasks => false
  }

  #guess that we need a special script file first if it exists
  if (platform == :mswin)
    options[:pretest] = File.exists?("#{ platform.to_s }_setup.bat") ? "#{ platform.to_s }_setup.bat" : nil
  else
    options[:pretest] = File.exists?("#{ platform.to_s }_setup.sh") ? "source #{ platform.to_s }_setup.sh" : nil
  end

  #merge in project settings if they can be found here
  yaml_options = YAML.load_file(main_filepath)
  if (yaml_options[:paths])
    options[:add_path] = yaml_options[:paths][:tools] || []
  else
    options[:add_path] = []
  end
  options[:graceful_fail]  = yaml_options[:graceful_fail] if yaml_options[:graceful_fail]
  options[:which_ceedling] = yaml_options[:project][:which_ceedling] if (yaml_options[:project] && yaml_options[:project][:which_ceedling])
  options[:default_tasks]  = yaml_options[:default_tasks] if yaml_options[:default_tasks]

  #sort through command line options
  ARGV.each do |v|
    case(v)
      when /^(?:new|examples?|templates?)$/
        puts "\nOops. You called ceedling with argument '#{v}'.\n" +
             "      This is an operation that will create a new project... \n" +
             "      but it looks like you're already in a project. If you really \n" +
             "      want to do this, try moving to an empty folder.\n\n"
        abort
      when /^help$/
        options[:list_tasks] = true
      when /^project:(\w+)/
        ENV['CEEDLING_USER_PROJECT_FILE'] = "#{$1}.yml"
      else
        options[:args].push(v)
    end
  end

  #add to the path
  if (options[:add_path] && !options[:add_path].empty?)
    path = ENV["PATH"]
    options[:add_path].each do |p|
      f = File.expand_path(File.dirname(__FILE__),p)
      path = (f + options[:path_connector] + path) unless path.include? f
    end
    ENV["PATH"] = path
  end

  # Load Ceedling (either through the rakefile OR directly)
  if (File.exists?("rakefile.rb"))
    load 'rakefile.rb'
  else
    if (options[:which_ceedling] == 'gem')
      require 'ceedling'
    else
      load "#{options[:which_ceedling]}/lib/ceedling.rb"
    end
    Ceedling.load_project
  end

  Rake.application.standard_exception_handling do
    if options[:list_tasks]
      # Display helpful task list when requested. This required us to dig into Rake internals a bit
      Rake.application.define_singleton_method(:name=) {|n| @name = n}
      Rake.application.name = 'ceedling'
      Rake.application.options.show_tasks = :tasks
      Rake.application.options.show_task_pattern = /^(?!.*build).*$/
      Rake.application.display_tasks_and_comments()
    else
      task :default => options[:default_tasks]

      # Run our Tasks!
      Rake.application.collect_command_line_tasks(options[:args])
      Rake.application.top_level
    end
  end
  true
#===================================================================================================================
end
