# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require "timeout"

module Vagrant
  module Plugin
    module V2
      # Base class for a communicator in Vagrant. A communicator is
      # responsible for communicating with a machine in some way. There
      # are various stages of Vagrant that require things such as uploading
      # files to the machine, executing shell commands, etc. Implementors
      # of this class are expected to provide this functionality in some
      # way.
      #
      # Note that a communicator must provide **all** of the methods
      # in this base class. There is currently no way for one communicator
      # to provide say a more efficient way of uploading a file, but not
      # provide shell execution. This sort of thing will come in a future
      # version.
      class Communicator

        autoload :Remote, "vagrant/plugin/remote/communicator"
        
        # This returns true/false depending on if the given machine
        # can be communicated with using this communicator. If this returns
        # `true`, then this class will be used as the primary communication
        # method for the machine.
        #
        # @return [Boolean]
        def self.match?(machine)
          true
        end

        # Initializes the communicator with the machine that we will be
        # communicating with. This base method does nothing (it doesn't
        # even store the machine in an instance variable for you), so you're
        # expected to override this and do something with the machine if
        # you care about it.
        #
        # @param [Machine] machine The machine this instance is expected to
        #   communicate with.
        def initialize(machine)
        end

        # Checks if the target machine is ready for communication. If this
        # returns true, then all the other methods for communicating with
        # the machine are expected to be functional.
        #
        # @return [Boolean]
        def ready?
          false
        end

        # wait_for_ready waits until the communicator is ready, blocking
        # until then. It will wait up to the given duration or raise an
        # exception if something goes wrong.
        #
        # @param [Integer] duration Timeout in seconds.
        # @return [Boolean] Will return true on successful connection
        #   or false on timeout.
        def wait_for_ready(duration)
          # By default, we implement a naive solution.
          begin
            Timeout.timeout(duration) do
              while true
                return true if ready?
                sleep 0.5
              end
            end
          rescue Timeout::Error
            # We timed out, we failed.
          end

          return false
        end

        # Download a file from the remote machine to the local machine.
        #
        # @param [String] from Path of the file on the remote machine.
        # @param [String] to Path of where to save the file locally.
        def download(from, to)
        end

        # Upload a file to the remote machine.
        #
        # @param [String] from Path of the file locally to upload.
        # @param [String] to Path of where to save the file on the remote
        #   machine.
        def upload(from, to)
        end

        # Execute a command on the remote machine. The exact semantics
        # of this method are up to the implementor, but in general the
        # users of this class will expect this to be a shell.
        #
        # This method gives you no way to write data back to the remote
        # machine, so only execute commands that don't expect input.
        #
        # @param [String] command Command to execute.
        # @yield [type, data] Realtime output of the command being executed.
        # @yieldparam [String] type Type of the output. This can be
        #   `:stdout`, `:stderr`, etc. The exact types are up to the
        #   implementor.
        # @yieldparam [String] data Data for the given output.
        # @return [Integer] Exit code of the command.
        def execute(command, opts=nil)
        end

        # Executes a command on the remote machine with administrative
        # privileges. See {#execute} for documentation, as the API is the
        # same.
        #
        # @see #execute
        def sudo(command, opts=nil)
        end

        # Executes a command and returns true if the command succeeded,
        # and false otherwise. By default, this executes as a normal user,
        # and it is up to the communicator implementation if they expose an
        # option for running tests as an administrator.
        #
        # @see #execute
        def test(command, opts=nil)
        end

        # Reset the communicator. For communicators which establish
        # a persistent connection to the remote machine, this connection
        # should be terminated and re-established. The communicator
        # instance should be in a "fresh" state after calling this method.
        def reset!
        end
      end
    end
  end
end
