package common

import (
	"fmt"
	"log"
	"os/exec"
	"runtime"
	"strconv"
	"strings"
)

// Driver is the interface that talks to Parallels and performs certain
// operations with it. Some of the operations on here may seem overly
// specific, but they were built specifically in mind to handle features
// of the Parallels builder for Packer, and to abstract differences in
// versions out of the builder steps, so sometimes the methods are
// extremely specific.
type Driver interface {
	// Compact a virtual disk image.
	CompactDisk(string) error

	// Adds new CD/DVD drive to the VM and returns name of this device
	DeviceAddCDROM(string, string) (string, error)

	// Get path to the first virtual disk image
	DiskPath(string) (string, error)

	// Import a VM
	Import(string, string, string, bool) error

	// Checks if the VM with the given name is running.
	IsRunning(string) (bool, error)

	// Stop stops a running machine, forcefully.
	Stop(string) error

	// Prlctl executes the given Prlctl command
	Prlctl(...string) error

	// Get the path to the Parallels Tools ISO for the given flavor.
	ToolsISOPath(string) (string, error)

	// Verify checks to make sure that this driver should function
	// properly. If there is any indication the driver can't function,
	// this will return an error.
	Verify() error

	// Version reads the version of Parallels that is installed.
	Version() (string, error)

	// Send scancodes to the vm using the prltype python script.
	SendKeyScanCodes(string, ...string) error

	// Apply default configuration settings to the virtual machine
	SetDefaultConfiguration(string) error

	// Finds the MAC address of the NIC nic0
	MAC(string) (string, error)

	// Finds the IP address of a VM connected that uses DHCP by its MAC address
	IPAddress(string) (string, error)
}

// NewDriver returns a new driver implementation for this version of Parallels
// Desktop, or an error if the driver couldn't be initialized.
func NewDriver() (Driver, error) {
	var drivers map[string]Driver
	var prlctlPath string
	var prlsrvctlPath string
	var supportedVersions []string
	DHCPLeaseFile := "/Library/Preferences/Parallels/parallels_dhcp_leases"

	if runtime.GOOS != "darwin" {
		return nil, fmt.Errorf(
			"Parallels builder works only on \"darwin\" platform!")
	}

	if prlctlPath == "" {
		var err error
		prlctlPath, err = exec.LookPath("prlctl")
		if err != nil {
			return nil, err
		}
	}

	log.Printf("prlctl path: %s", prlctlPath)

	if prlsrvctlPath == "" {
		var err error
		prlsrvctlPath, err = exec.LookPath("prlsrvctl")
		if err != nil {
			return nil, err
		}
	}

	log.Printf("prlsrvctl path: %s", prlsrvctlPath)

	drivers = map[string]Driver{
		"11": &Parallels11Driver{
			Parallels9Driver: Parallels9Driver{
				PrlctlPath:    prlctlPath,
				PrlsrvctlPath: prlsrvctlPath,
				dhcpLeaseFile: DHCPLeaseFile,
			},
		},
		"10": &Parallels10Driver{
			Parallels9Driver: Parallels9Driver{
				PrlctlPath:    prlctlPath,
				PrlsrvctlPath: prlsrvctlPath,
				dhcpLeaseFile: DHCPLeaseFile,
			},
		},
		"9": &Parallels9Driver{
			PrlctlPath:    prlctlPath,
			PrlsrvctlPath: prlsrvctlPath,
			dhcpLeaseFile: DHCPLeaseFile,
		},
	}

	for v, d := range drivers {
		version, _ := d.Version()
		if strings.HasPrefix(version, v) {
			if err := d.Verify(); err != nil {
				return nil, err
			}
			return d, nil
		}
		supportedVersions = append(supportedVersions, v)
	}

	latestDriver := 11
	version, _ := drivers[strconv.Itoa(latestDriver)].Version()
	majVer, _ := strconv.Atoi(strings.SplitN(version, ".", 2)[0])
	log.Printf("Parallels version: %s", version)
	if majVer > latestDriver {
		log.Printf("Your version of Parallels Desktop for Mac is %s, Packer will use driver for version %d.", version, latestDriver)
		return drivers[strconv.Itoa(latestDriver)], nil
	}

	return nil, fmt.Errorf(
		"Unable to initialize any driver. Supported Parallels Desktop versions: "+
			"%s\n", strings.Join(supportedVersions, ", "))
}
