module Vagrant
  module Provisioners
    # This class implements provisioning via chef-solo.
    class ChefSolo < Chef
      def prepare
        share_cookbook_folders
        share_role_folders
      end

      def provision!
        verify_binary("chef-solo")
        chown_provisioning_folder
        setup_json
        setup_solo_config
        run_chef_solo
      end

      def share_cookbook_folders
        host_cookbook_paths.each_with_index do |cookbook, i|
          env.config.vm.share_folder("v-csc-#{i}", cookbook_path(i), cookbook)
        end
      end

      def share_role_folders
        host_role_paths.each_with_index do |role, i|
          env.config.vm.share_folder("v-csr-#{i}", role_path(i), role)
        end
      end

      def setup_solo_config
        setup_config("chef_solo_solo", "solo.rb", {
          :node_name => env.config.chef.node_name,
          :provisioning_path => env.config.chef.provisioning_path,
          :cookbooks_path => cookbooks_path,
          :recipe_url => env.config.chef.recipe_url,
          :roles_path => roles_path,
        })
      end

      def run_chef_solo
        command = "cd #{env.config.chef.provisioning_path} && sudo -E chef-solo -c solo.rb -j dna.json"

        env.ui.info I18n.t("vagrant.provisioners.chef.running_solo")
        vm.ssh.execute do |ssh|
          ssh.exec!(command) do |channel, type, data|
            ssh.check_exit_status(data, command) if type == :exit_status
            env.ui.info("#{data}: #{type}") if type != :exit_status
          end
        end
      end

      def host_folder_paths(paths)
        # Convert single cookbook paths such as "cookbooks" or [:vm, "cookbooks"]
        # into a proper array representation.
        paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)

        paths.inject([]) do |acc, path|
          path = [:host, path] if !path.is_a?(Array)
          type, path = path

          acc << File.expand_path(path, env.root_path) if type == :host
          acc
        end
      end

      def folder_path(*args)
        File.join(env.config.chef.provisioning_path, args.join("-"))
      end

      def folders_path(folders, folder)
        # Convert single cookbook paths such as "cookbooks" or [:vm, "cookbooks"]
        # into a proper array representation.
        folders = [folders] if folders.is_a?(String) || folders.first.is_a?(Symbol)

        # Convert each path to the proper absolute path depending on if the path
        # is a host path or a VM path
        result = []
        folders.each_with_index do |path, i|
          path = [:host, path] if !path.is_a?(Array)
          type, path = path

          result << folder_path(folder, i) if type == :host
          result << folder_path(path) if type == :vm
        end

        # We're lucky that ruby's string and array syntax for strings is the
        # same as JSON, so we can just convert to JSON here and use that
        result = result[0].to_s if result.length == 1
        result
      end

      def host_cookbook_paths
        host_folder_paths(env.config.chef.cookbooks_path)
      end

      def host_role_paths
        host_folder_paths(env.config.chef.roles_path)
      end

      def cookbook_path(i)
        folder_path("cookbooks", i)
      end

      def role_path(i)
        folder_path("roles", i)
      end

      def cookbooks_path
        folders_path(env.config.chef.cookbooks_path, "cookbooks").to_json
      end

      def roles_path
        folders_path(env.config.chef.roles_path, "roles").to_json
      end
    end
  end
end
