module JumpStart
  class Base
        
    # Accessor methods to make testing input or output easier.
    attr_accessor :input, :output

    # Monkeypatch gets to make testing easier.
    def gets(*args)
      @input.gets(*args)
    end
    
    # Monkeypatch puts to make testing easier.
    def puts(*args)
      @output.puts(*args)    
    end
    
    def initialize(args)
      # setup for testing input
      @input  = $stdin
      # setup for testing output
      @output = $stdout
      # set the name of the project from the first argument passed, or from the module instance variable JumpStart::Setup.default_template_name if no argument passed.
      @project_name = args.shift.dup if args[0] != nil
      if args[0] != nil
        @template_name = args.shift.dup
      elsif JumpStart::Setup.default_template_name.nil? != nil
        @template_name = JumpStart::Setup.default_template_name
      end
      # set instance variable @template_path as the directory to read templates from.
      @template_path = FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name)
    end

    # TODO Ensure that if jumpstart is launched with two arguments they are parsed as @project_name and @template_name, and the command is launched without any menu display.
    # TODO Ensure that if jumpstart is launched with one argument it is parsed as @project_name, and if JumpStart::Setup.default_template_name exists then the command is launched without any menu display.
    # TODO Write integration tests.
    # TODO Document methods for RDOC
    # Finish README etc for github
    # TODO try and find a way to automatically reset jumpstart_setup.yml while in development.
    
    # Look into moving @install_path or refactoring to make setting this variable easier.
    def set_config_file_options
      if @template_name.nil?
        jumpstart_menu
      elsif File.exists?(FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name, "/jumpstart_config/", "#{@template_name}.yml"))
        @config_file = YAML.load_file(FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name, "/jumpstart_config/", "#{@template_name}.yml"))
        @install_command ||= @config_file[:install_command]
        @install_command_args ||= @config_file[:install_command_args]
        @replace_strings ||= @config_file[:replace_strings].each {|x| x}
        @install_path ||= FileUtils.join_paths(@config_file[:install_path])
      else
        jumpstart_menu
      end
    end
    
    def check_setup
      set_config_file_options
      lookup_existing_templates
      check_project_name
      check_template_name
      check_template_path
      check_install_path
    end
    
    # set up instance variable containing an array that will be populated with existing jumpstart templates
    def lookup_existing_templates
      @existing_templates = []
      template_dirs = Dir.entries(JumpStart::Setup.templates_path) - IGNORE_DIRS
      template_dirs.each do |x|
        if File.directory?(FileUtils.join_paths(JumpStart::Setup.templates_path, x))
          if Dir.entries(FileUtils.join_paths(JumpStart::Setup.templates_path, x)).include? "jumpstart_config"
            if File.exists?(FileUtils.join_paths(JumpStart::Setup.templates_path, x, '/jumpstart_config/', "#{x}.yml"))
              @existing_templates << x
            end
          end
        end
      end
    end
    
    def start
      puts "\n******************************************************************************************************************************************\n\n"
      puts "  JumpStarting....\n".purple
      check_setup
      execute_install_command
      run_scripts_from_yaml(:run_after_install_command)
      parse_template_dir
      create_dirs
      populate_files_from_whole_templates
      populate_files_from_append_templates
      populate_files_from_line_templates
      remove_unwanted_files
      run_scripts_from_yaml(:run_after_jumpstart)
      check_for_strings_to_replace
      check_local_nginx_configuration
      exit_with_success
    end
    
    def check_replace_string_pairs_for_project_name_sub(hash)
      unless @project_name.nil?
        hash.each do |key, value|
          if key == :project_name
            hash[key] = @project_name
          end
        end
      end
      hash
    end
    
    private
    
    def check_project_name
      if @project_name.nil? || @project_name.empty?
        puts "\n  Enter a name for your project.".yellow
        @project_name = gets.chomp.strip
        check_project_name
      elsif @project_name.length < 3
        puts "\n  The name #{@project_name} is too short. Please enter a name at least 3 characters long.".red
        @project_name = gets.chomp.strip
        check_project_name
      elsif @project_name.match(/^\W/)
        puts "\n  #{@project_name} begins with an invalid character. Please enter a name thats starts with a letter or a number.".red
        @project_name = gets.chomp.strip
        check_project_name
      else
        @project_name
      end
    end
    
    def check_template_name
      if @template_name.nil? || @template_name.empty?
        jumpstart_menu
      end
    end
    
    def check_template_path
      begin
        Dir.chdir(@template_path)
      rescue
        puts "\nThe directory #{@template_path.red} could not be found, or you do not have the correct permissions to access it."
        exit_normal
      end
    end

    # TODO test check_install_path setting @install_path = FileUtils.pwd from prompt.
    def check_install_path
      @install_path = FileUtils.pwd if @install_path.nil? || @install_path.empty?
      if File.directory?(FileUtils.join_paths(@install_path, @project_name))
        puts "\nThe directory #{FileUtils.join_paths(@install_path, @project_name).red} already exists.\nAs this is the location you have specified for creating your new project jumpstart will now exit to avoid overwriting anything."
        exit_normal
      end
      true
    end
           
    def create_template
      if File.directory?(FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name))
        puts "\nThe directory #{FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name).red} already exists. The template will not be created."
        exit_normal
      else
        FileUtils.mkdir_p(FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name, "/jumpstart_config"))
        yaml = IO.read(FileUtils.join_paths(ROOT_PATH, "/source_templates/template_config.yml"))
        File.open(FileUtils.join_paths(JumpStart::Setup.templates_path, @template_name, "/jumpstart_config", "#{@template_name}.yml"), 'w') do |file|
          file.puts yaml
        end
        puts "The template #{@template_name.green} has been generated.\n"
      end
    end
        
    def jumpstart_menu
      puts "\n\n******************************************************************************************************************************************\n\n"
      puts "  JUMPSTART MENU\n".purple
      puts "  Here are your options:\n\n"
      puts "  1".yellow + " Create a new project from an existing template.\n"
      puts "  2".yellow + " Create a new template.\n"
      puts "  3".yellow + " Set the default template.\n"
      puts "  4".yellow + " Set the templates directory.\n\n"
      puts "  x".yellow + " Exit jumpstart\n\n"
      puts "******************************************************************************************************************************************\n\n"
      jumpstart_menu_options
    end
    
    def jumpstart_menu_options
      lookup_existing_templates
      input = gets.chomp.strip
      case
      when input == "1"
        new_project_from_template_menu
      when input == "2"
        new_template_menu
      when input == "3"
        set_default_template_menu
      when input == "4"
        templates_dir_menu
      when input == "x"
        exit_normal
      else
        puts "That command hasn't been understood. Try again!".red
        jumpstart_menu_options
      end
    end
    
    def new_project_from_template_menu
      puts "\n\n******************************************************************************************************************************************\n\n"
      puts "  CREATE A NEW JUMPSTART PROJECT FROM AN EXISTING TEMPLATE\n\n".purple
      puts "  Type a number for the template that you want.\n\n"
      count = 0
      unless @existing_templates.nil? || @existing_templates.empty?
        @existing_templates.each do |t|
          count += 1
          puts "  #{count.to_s.yellow} #{t}"
        end
      end
      puts "\n  b".yellow + " Back to main menu."
      puts "\n  x".yellow + " Exit jumpstart\n\n"
      puts "******************************************************************************************************************************************\n\n"
      new_project_from_template_options
    end
    
    def new_project_from_template_options
      input = gets.chomp.strip
      case
      when input.to_i <= @existing_templates.count && input.to_i > 0
        @template_name = @existing_templates[(input.to_i - 1)]
        check_project_name
        project = JumpStart::Base.new([@project_name, @template_name])
        project.check_setup
        project.start
      when input == "b"
        jumpstart_menu
      when input == "x"
        exit_normal
      else
        puts "That command hasn't been understood. Try again!".red
      end
    end
    
    def new_template_menu
      puts "\n\n******************************************************************************************************************************************\n\n"
      puts "  CREATE A NEW JUMPSTART TEMPLATE\n".purple
      puts "  Existing templates:\n"
      lookup_existing_templates
      @existing_templates.each do |x|
        puts "  #{x.green}\n"
      end
      new_template_options
    end
    
    # TODO add functionality for duplicating an existing template
    def new_template_options
      puts "\n  Enter a unique name for the new template.\n".yellow
      input = gets.chomp.strip
      case
      when @existing_templates.include?(input)
        puts "  A template of the name ".red + input.red_bold + " already exists.".red
        new_template_options
      when input.length < 3
        puts "  The template name ".red + input.red_bold + " is too short. Please enter a name that is at least 3 characters long.".red
        new_template_options
      when input.match(/^\W/)
        puts "  The template name ".red + input.red_bold + " begins with an invalid character. Please enter a name that begins with a letter or a number.".red
        new_template_options
      else
        FileUtils.mkdir_p(FileUtils.join_paths(JumpStart::Setup.templates_path, input, "jumpstart_config"))
        FileUtils.cp(FileUtils.join_paths(ROOT_PATH, "source_templates/template_config.yml"), FileUtils.join_paths(JumpStart::Setup.templates_path, input, "jumpstart_config", "#{input}.yml"))
        puts "  The template ".green + input.green_bold + " has been created in your default jumpstart template directory ".green + JumpStart::Setup.templates_path.green_bold + " ready for editing.".green
        jumpstart_menu
      end
    end
    
    def set_default_template_menu
      puts "\n\n******************************************************************************************************************************************\n\n"
      puts "  JUMPSTART DEFAULT TEMPLATE OPTIONS\n\n".purple
      count = 0
      @existing_templates.each do |t|
        count += 1
        puts "  #{count.to_s.yellow} #{t}"
      end
      puts "\n  b".yellow + " Back to main menu.\n\n"
      puts "  x".yellow + " Exit jumpstart\n\n"
      puts "******************************************************************************************************************************************\n\n"
      set_default_template_options
    end
    
    # Sets the default template to be used by JumpStart.
    def set_default_template_options
      input = gets.chomp.strip
      case
      when input.to_i <= @existing_templates.count && input.to_i > 0
        JumpStart::Setup.default_template_name = @existing_templates[(input.to_i - 1)]
        JumpStart::Setup.dump_jumpstart_setup_yaml
        puts "  The default jumpstart template has been set to: #{JumpStart::Setup.default_template_name.green}"
        jumpstart_menu
      when input == "b"
        jumpstart_menu
      when input == "x"
        exit_normal
      else
        puts "That command hasn't been understood. Try again!".red
        set_default_template_options
      end
    end
    
    def templates_dir_menu
      puts "\n\n******************************************************************************************************************************************\n\n"
      puts "  JUMPSTART TEMPLATES DIRECTORY OPTIONS\n\n".purple
      puts "  1".yellow + " Set templates directory.\n"
      puts "  2".yellow + " Reset templates directory to default\n\n"
      puts "  b".yellow + " Back to main menu.\n\n"
      puts "  x".yellow + " Exit jumpstart\n\n"
      puts "******************************************************************************************************************************************\n\n"
      templates_dir_options
    end
    
    def templates_dir_options
      input = gets.chomp.strip
      case
      when input == "1"
        set_templates_dir
      when input == "2"
        reset_templates_dir_to_default_check
      when input == "b"
        jumpstart_menu
      when input == "x"
        exit_normal
      else
        puts "That command hasn't been understood. Try again!".red
        templates_dir_options
      end
    end
    
    # Sets the path for templates to be used by JumpStart.
    def set_templates_dir
      puts "Please enter the absolute path for the directory that you would like to contain your jumpstart templates."
      input = gets.chomp.strip
      root_path = input.sub(/\/\w*\/*$/, '')
      case
      when File.directory?(input)
        puts "A directory of that name already exists, please choose a directory that does not exist."
        set_templates_dir
      when File.directory?(root_path)
        begin
          Dir.chdir(root_path)
          Dir.mkdir(input)
          files_and_dirs = FileUtils.sort_contained_files_and_dirs(JumpStart::Setup.templates_path)
          puts "\nCopying existing templates to #{input}"
          files_and_dirs[:dirs].each {|x| FileUtils.mkdir_p(FileUtils.join_paths(input, x))}
          files_and_dirs[:files].each {|x| FileUtils.cp(FileUtils.join_paths(JumpStart::Setup.templates_path, x), FileUtils.join_paths(input, x)) }
          JumpStart::Setup.templates_path = input.to_s
          JumpStart::Setup.dump_jumpstart_setup_yaml
          puts "\nTransfer complete!".green
          jumpstart_menu
        rescue
          puts "It looks like you do not have the correct permissions to create a directory in #{root_path.red}"
        end
      end
    end
    
    # Checks to see if the JumpStart template directory should be reset to the default location. (within the gem.)
    def reset_templates_dir_to_default_check
      if JumpStart::Setup.templates_path == "#{ROOT_PATH}/jumpstart_templates"
        puts "  You do not need to reset the jumpstart templates directory, it is already set to: #{ROOT_PATH}/jumpstart_templates\n\n".red
        templates_dir_menu
      else  
        puts "  Resetting the jumpstart templates directory to the default: #{ROOT_PATH}/jumpstart_templates\n\n"
        @current_files_and_dirs = FileUtils.sort_contained_files_and_dirs(JumpStart::Setup.templates_path)
        puts "  Moving your jumpstart templates back to the default directory will delete any templates that are currently there. Proceed?\n".yellow
        puts "  Type yes (" + "y".yellow + ") or no (" + "n".yellow + ")\n\n"
        reset_templates_dir_to_default_set
      end
    end
    
    # Resets the JumpStart template directory to the default location. (within the gem.)
    def reset_templates_dir_to_default_set
      input = gets.chomp.strip
      if input == "yes" || input == "y"
        FileUtils.delete_dir_contents(FileUtils.join_paths(ROOT_PATH, '/jumpstart_templates'))
        FileUtils.touch(FileUtils.join_paths(ROOT_PATH, '.gitignore'))
        @current_files_and_dirs[:dirs].each {|x| FileUtils.mkdir_p(FileUtils.join_paths(ROOT_PATH, '/jumpstart_templates', x))}
        @current_files_and_dirs[:files].each {|x| FileUtils.cp(FileUtils.join_paths(JumpStart::Setup.templates_path, x), FileUtils.join_paths(ROOT_PATH, '/jumpstart_templates', x)) }
        JumpStart::Setup.templates_path = FileUtils.join_paths(ROOT_PATH, '/jumpstart_templates')
        JumpStart::Setup.dump_jumpstart_setup_yaml
        puts "\n  SUCCESS! the jumpstart templates directory has been set to the default: #{ROOT_PATH}/jumpstart_templates".green
        templates_dir_menu
      elsif input == "no" || input == "n"
        puts "\n You have chosen not to move the jumpstart templates directory, nothing has been changed."
        templates_dir_menu
      else
        puts "\n The command you entered could not be understood, please enter yes '" + "y".yellow + "' or no '" + "n".yellow + "'"
        reset_templates_dir_to_default_set
      end
    end

    def execute_install_command
      Dir.chdir(@install_path)
      unless @install_command.nil? || @install_command.empty?
        puts "Executing command: #{@install_command.green} #{@project_name.green} #{@install_command_args.green}"
        system "#{@install_command} #{@project_name} #{@install_command_args}"
      end
    end

    def parse_template_dir
      @dir_list = []
      file_list = []
      @append_templates = []
      @line_templates = []
      @whole_templates = []
      Find.find(@template_path) do |x|
        case
        when File.file?(x) && x !~ /\/jumpstart_config/ then
          file_list << x.sub!(@template_path, '')
        when File.directory?(x) && x !~ /\/jumpstart_config/ then
          @dir_list << x.sub!(@template_path, '')
        when File.file?(x) && x =~ /\/jumpstart_config\/nginx.local.conf/ then
          @nginx_local_template = x
        when File.file?(x) && x =~ /\/jumpstart_config\/nginx.remote.conf/ then
          @nginx_remote_template = x
        end
      end
      file_list.each do |file|
        if file =~ /_([lL]?)\._{1}\w*/
          @append_templates << file
        elsif file =~ /_(\d+)\._{1}\w*/
          @line_templates << file
        else
          @whole_templates << file
        end
      end
    end
    
    # makes folders for the project
    def create_dirs
      @dir_list.each {|dir| FileUtils.mkdir_p(FileUtils.join_paths(@install_path, @project_name, dir)) } unless @dir_list.nil?
    end
    
    # create files from whole templates
    def populate_files_from_whole_templates
      @whole_templates.each {|x| FileUtils.cp(FileUtils.join_paths(@template_path, x), FileUtils.join_paths(@install_path, @project_name, x)) } unless @whole_templates.nil?
    end

    def populate_files_from_append_templates
      @append_templates.each do |x|
        new_name = x.sub(/_([lL]?)\._{1}/, '')
        FileUtils.touch(FileUtils.join_paths(@install_path, @project_name, new_name))
        FileUtils.append_to_end_of_file(FileUtils.join_paths(@template_path, x), FileUtils.join_paths(@install_path, @project_name, new_name), JumpStart::Base.remove_last_line?(x))
      end
    end
    
    def populate_files_from_line_templates
      @line_templates.each do |x|
        new_name = x.sub(/_(\d+)\._{1}/, '')
        FileUtils.touch(FileUtils.join_paths(@install_path, @project_name, new_name))
        FileUtils.insert_text_at_line_number(FileUtils.join_paths(@template_path, x), FileUtils.join_paths(@install_path, @project_name, new_name), JumpStart::Base.get_line_number(x))
      end
    end

    def check_local_nginx_configuration
      if @nginx_local_template.nil? || @config_file[:local_nginx_conf].nil?
        puts "  \nNginx will not be configured as options have not been set for this."
      else
        FileUtils.config_nginx(@nginx_local_template, @config_file[:local_nginx_conf], @project_name)
        FileUtils.config_hosts("/etc/hosts", @project_name)
      end
    end
    
    def remove_unwanted_files
      file_array = []
      root_path = FileUtils.join_paths(@install_path, @project_name)
      @config_file[:remove_files].each do |file|
        file_array << FileUtils.join_paths(root_path, file)
      end
      FileUtils.remove_files(file_array)
    end
    
    def run_scripts_from_yaml(script_name)
      unless @config_file[script_name].nil? || @config_file[script_name].empty?
        begin
          Dir.chdir(FileUtils.join_paths(@install_path, @project_name))
          @config_file[script_name].each do |x|
            puts "\nExecuting command: #{x.green}"
            system "#{x}"
          end
        rescue
          puts "\nCould not access the directory #{FileUtils.join_paths(@install_path, @project_name).red}.\nIn the interest of safety JumpStart will NOT run any YAML scripts from #{script_name.to_s.red_bold} until it can change into the new projects home directory."
        end
      end
    end
    
    def check_for_strings_to_replace
      if @replace_strings.nil? || @replace_strings.empty?
        false
      else
        puts "\nChecking for strings to replace inside files...\n\n"
        @replace_strings.each do |file|
          puts "Target file: #{file[:target_path].green}\n"
          puts "Strings to replace:\n\n"
          check_replace_string_pairs_for_project_name_sub(file[:symbols])
          file[:symbols].each do |x,y|
            puts "Key:    #{x.to_s.green}"
            puts "Value:  #{y.to_s.green}\n\n"
          end
          puts "\n"
          path = FileUtils.join_paths(@install_path, @project_name, file[:target_path])
          FileUtils.replace_strings(path, file[:symbols])
        end
      end
    end
    
    def exit_with_success
      puts "\n\n  Exiting JumpStart...".purple
      puts "\n  Success! ".green + @project_name.green_bold + " has been created at: ".green + FileUtils.join_paths(@install_path, @project_name).green_bold + "\n\n".green
      puts "******************************************************************************************************************************************\n"
      JumpStart::Setup.dump_jumpstart_setup_yaml
      exit
    end

    def exit_normal
      puts "\n\n  Exiting JumpStart...".purple
      puts "\n  Goodbye!\n\n"
      puts "******************************************************************************************************************************************\n"
      JumpStart::Setup.dump_jumpstart_setup_yaml
      exit
    end
      
    class << self

      def get_line_number(file_name)
        if file_name.match(/_(\d+)\._\w*/)
          number = file_name.match(/_(\d+)\._\w*/)[1]
          number.to_i
        else
          false
        end
      end

      def remove_last_line?(file_name)
        if file_name.match(/_([lL]{1})\._{1}\w*/)
          true
        else
          false
        end
      end
      
    end
      
  end
end