package compute

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/hashicorp/go-oracle-terraform/client"
)

const waitForInstanceReadyPollInterval = 10 * time.Second
const waitForInstanceReadyTimeout = 3600 * time.Second
const waitForInstanceDeletePollInterval = 10 * time.Second
const waitForInstanceDeleteTimeout = 3600 * time.Second

// InstancesClient is a client for the Instance functions of the Compute API.
type InstancesClient struct {
	ResourceClient
}

// Instances obtains an InstancesClient which can be used to access to the
// Instance functions of the Compute API
func (c *Client) Instances() *InstancesClient {
	return &InstancesClient{
		ResourceClient: ResourceClient{
			Client:              c,
			ResourceDescription: "instance",
			ContainerPath:       "/launchplan/",
			ResourceRootPath:    "/instance",
		}}
}

// InstanceState specifies the constants that an instance state can be in
type InstanceState string

const (
	// InstanceRunning - running
	InstanceRunning InstanceState = "running"
	// InstanceInitializing - initializing
	InstanceInitializing InstanceState = "initializing"
	// InstancePreparing - preparing
	InstancePreparing InstanceState = "preparing"
	// InstanceStarting - starting
	InstanceStarting InstanceState = "starting"
	// InstanceStopping - stopping
	InstanceStopping InstanceState = "stopping"
	// InstanceShutdown - shutdown
	InstanceShutdown InstanceState = "shutdown"
	// InstanceQueued - queued
	InstanceQueued InstanceState = "queued"
	// InstanceError - error
	InstanceError InstanceState = "error"
)

// InstanceDesiredState specifies the constants that for a desired instance state
type InstanceDesiredState string

const (
	// InstanceDesiredRunning - running
	InstanceDesiredRunning InstanceDesiredState = "running"
	// InstanceDesiredShutdown - shutdown
	InstanceDesiredShutdown InstanceDesiredState = "shutdown"
)

// InstanceInfo represents the Compute API's view of the state of an instance.
type InstanceInfo struct {
	// The ID for the instance. Set by the SDK based on the request - not the API.
	ID string

	// The availability domain for the instance
	AvailabilityDomain string `json:"availability_domain"`

	// The default domain to use for the hostname and DNS lookups
	Domain string `json:"domain"`

	// The desired state of an instance
	DesiredState InstanceDesiredState `json:"desired_state"`

	// The reason for the instance going to error state, if available.
	ErrorReason string `json:"error_reason"`

	// SSH Server Fingerprint presented by the instance
	Fingerprint string `json:"fingerprint"`

	// Fully Qualified Domain Name
	FQDN string `json:"name"`

	// The hostname for the instance
	Hostname string `json:"hostname"`

	// The format of the image
	ImageFormat string `json:"image_format"`

	// Name of imagelist to be launched.
	ImageList string `json:"imagelist"`

	// IP address of the instance.
	IPAddress string `json:"ip"`

	// A label assigned by the user, specifically for defining inter-instance relationships.
	Label string `json:"label"`

	// Name of this instance, generated by the server.
	Name string

	// A list of strings specifying arbitrary tags on nodes to be matched on placement.
	PlacementRequirements []string `json:"placement_requirements"`

	// The OS platform for the instance.
	Platform string `json:"platform"`

	// The priority at which this instance will be run
	Priority string `json:"priority"`

	// Reference to the QuotaReservation, to be destroyed with the instance
	QuotaReservation string `json:"quota_reservation"`

	// Array of relationship specifications to be satisfied on this instance's placement
	Relationships []string `json:"relationships"`

	// Resolvers to use instead of the default resolvers
	Resolvers []string `json:"resolvers"`

	// Type of instance, as defined on site configuration.
	Shape string `json:"shape"`

	// Site to run on
	Site string `json:"site"`

	// ID's of SSH keys that will be exposed to the instance.
	SSHKeys []string `json:"sshkeys"`

	// The start time of the instance
	StartTime string `json:"start_time"`

	// State of the instance.
	State InstanceState `json:"state"`

	// The Storage Attachment information.
	Storage []StorageAttachment `json:"storage_attachments"`

	// Array of tags associated with the instance.
	Tags []string `json:"tags"`

	// vCable for this instance.
	VCableID string `json:"vcable_id"`

	// IP Address and port of the VNC console for the instance
	VNC string `json:"vnc"`

	// Add PTR records for the hostname
	ReverseDNS bool `json:"reverse_dns"`

	// Specify if the devices created for the instance are virtio devices. If not specified, the default
	// will come from the cluster configuration file
	Virtio bool `json:"virtio,omitempty"`

	// Optional ImageListEntry number. Default will be used if not specified
	Entry int `json:"entry"`

	// Boot order list.
	BootOrder []int `json:"boot_order"`

	// A dictionary of attributes to be made available to the instance.
	// A value with the key "userdata" will be made available in an EC2-compatible manner.
	Attributes map[string]interface{} `json:"attributes"`

	// Mapping of to network specifiers for virtual NICs to be attached to this instance.
	Networking map[string]NetworkingInfo `json:"networking"`
}

// StorageAttachment specifies the parameters for a storage attachment
type StorageAttachment struct {
	// The index number for the volume.
	Index int `json:"index"`

	// The three-part name (/Compute-identity_domain/user/object) of the storage attachment.
	Name string `json:"name"`

	// The three-part name (/Compute-identity_domain/user/object) of the storage volume attached to the instance.
	StorageVolumeName string `json:"storage_volume_name"`
}

func (i *InstanceInfo) getInstanceName() string {
	return fmt.Sprintf(cmpQualifiedName, i.Name, i.ID)
}

// CreateInstanceInput specifies the parameters needed to create an instance
type CreateInstanceInput struct {
	// A dictionary of user-defined attributes to be made available to the instance.
	// Optional
	Attributes map[string]interface{} `json:"attributes"`
	// Boot order list
	// Optional
	BootOrder []int `json:"boot_order,omitempty"`
	// The desired state of the opc instance. Can only be `running` or `shutdown`
	// Omits if empty.
	// Optional
	DesiredState InstanceDesiredState `json:"desired_state,omitempty"`
	// ImageListEntry number. Default will be used if not specified
	// Optional
	Entry int `json:"entry,omitempty"`
	// The host name assigned to the instance. On an Oracle Linux instance,
	// this host name is displayed in response to the hostname command.
	// Only relative DNS is supported. The domain name is suffixed to the host name
	// that you specify. The host name must not end with a period. If you don't specify a
	// host name, then a name is generated automatically.
	// Optional
	Hostname string `json:"hostname"`
	// Name of imagelist to be launched.
	// Optional
	ImageList string `json:"imagelist"`
	// A label assigned by the user, specifically for defining inter-instance relationships.
	// Optional
	Label string `json:"label"`
	// Name of this instance, generated by the server.
	// Optional
	Name string `json:"name"`
	// Networking information.
	// Optional
	Networking map[string]NetworkingInfo `json:"networking"`
	// If set to true (default), then reverse DNS records are created.
	// If set to false, no reverse DNS records are created.
	// Optional
	ReverseDNS bool `json:"reverse_dns,omitempty"`
	// Type of instance, as defined on site configuration.
	// Required
	Shape string `json:"shape"`
	// A list of the Storage Attachments you want to associate with the instance.
	// Optional
	Storage []StorageAttachmentInput `json:"storage_attachments,omitempty"`
	// A list of the SSH public keys that you want to associate with the instance.
	// Optional
	SSHKeys []string `json:"sshkeys"`
	// A list of tags to be supplied to the instance
	// Optional
	Tags []string `json:"tags"`
	// Time to wait for an instance to be ready
	Timeout time.Duration `json:"-"`
}

// StorageAttachmentInput specifies the attributes needed to attach a storage attachment
type StorageAttachmentInput struct {
	// The index number for the volume. The allowed range is 1 to 10.
	// If you want to use a storage volume as the boot disk for an instance, you must specify the index number for that volume as 1.
	// The index determines the device name by which the volume is exposed to the instance.
	Index int `json:"index"`
	// The three-part name (/Compute-identity_domain/user/object) of the storage volume that you want to attach to the instance.
	// Note that volumes attached to an instance at launch time can't be detached.
	Volume string `json:"volume"`
}

// ReservationPrefix - ipreservation
const ReservationPrefix = "ipreservation"

// ReservationIPPrefix - network/v1/ipreservation
const ReservationIPPrefix = "network/v1/ipreservation"

// NICModel specifies the constants that a nic model can be in
type NICModel string

const (
	// NICDefaultModel - e1000
	NICDefaultModel NICModel = "e1000"
)

// NetworkingInfo struct of Networking info from a populated instance, or to be used as input to create an instance
type NetworkingInfo struct {
	// The DNS name for the Shared network (Required)
	// DNS A Record for an IP Network (Optional)
	DNS []string `json:"dns,omitempty"`
	// IP Network only.
	// If you want to associate a static private IP Address,
	// specify that here within the range of the supplied IPNetwork attribute.
	// Optional
	IPAddress string `json:"ip,omitempty"`
	// IP Network only.
	// The name of the IP Network you want to add the instance to.
	// Required
	IPNetwork string `json:"ipnetwork,omitempty"`
	// IP Network only.
	// Set interface as default gateway for all traffic
	// Optional
	IsDefaultGateway bool `json:"is_default_gateway,omitempty"`
	// IP Network only.
	// The hexadecimal MAC Address of the interface
	// Optional
	MACAddress string `json:"address,omitempty"`
	// Shared Network only.
	// The type of NIC used. Must be set to 'e1000'
	// Required
	Model NICModel `json:"model,omitempty"`
	// IP Network and Shared Network
	// The name servers that are sent through DHCP as option 6.
	// You can specify a maximum of eight name server IP addresses per interface.
	// Optional
	NameServers []string `json:"name_servers,omitempty"`
	// The names of an IP Reservation to associate in an IP Network (Optional)
	// Indicates whether a temporary or permanent public IP Address should be assigned
	// in a Shared Network (Required)
	Nat []string `json:"nat,omitempty"`
	// IP Network and Shared Network
	// The search domains that should be sent through DHCP as option 119.
	// You can enter a maximum of eight search domain zones per interface.
	// Optional
	SearchDomains []string `json:"search_domains,omitempty"`
	// Shared Network only.
	// The security lists that you want to add the instance to
	// Required
	SecLists []string `json:"seclists,omitempty"`
	// IP Network Only
	// The name of the vNIC
	// Optional
	Vnic string `json:"vnic,omitempty"`
	// IP Network only.
	// The names of the vNICSets you want to add the interface to.
	// Optional
	VnicSets []string `json:"vnicsets,omitempty"`
}

// LaunchPlanInput defines a launch plan, used to launch instances with the supplied InstanceSpec(s)
type LaunchPlanInput struct {
	// Describes an array of instances which should be launched
	Instances []CreateInstanceInput `json:"instances"`
	// Time to wait between polls to check status
	PollInterval time.Duration `json:"-"`
	// Time to wait for instance boot
	Timeout time.Duration `json:"-"`
}

// LaunchPlanResponse details the response recieved when submitting a launchplan
type LaunchPlanResponse struct {
	// An array of instances which have been launched
	Instances []InstanceInfo `json:"instances"`
}

// CreateInstance creates and submits a LaunchPlan to launch a new instance.
func (c *InstancesClient) CreateInstance(input *CreateInstanceInput) (*InstanceInfo, error) {
	qualifiedSSHKeys := []string{}
	for _, key := range input.SSHKeys {
		qualifiedSSHKeys = append(qualifiedSSHKeys, c.getQualifiedName(key))
	}

	input.SSHKeys = qualifiedSSHKeys

	qualifiedStorageAttachments := []StorageAttachmentInput{}
	for _, attachment := range input.Storage {
		qualifiedStorageAttachments = append(qualifiedStorageAttachments, StorageAttachmentInput{
			Index:  attachment.Index,
			Volume: c.getQualifiedName(attachment.Volume),
		})
	}
	input.Storage = qualifiedStorageAttachments

	input.Networking = c.qualifyNetworking(input.Networking)

	input.Name = c.getQualifiedName(input.Name)

	plan := LaunchPlanInput{
		Instances: []CreateInstanceInput{*input},
		Timeout:   input.Timeout,
	}

	var (
		instanceInfo  *InstanceInfo
		instanceError error
	)
	for i := 0; i < *c.Client.client.MaxRetries; i++ {
		c.client.DebugLogString(fmt.Sprintf("(Iteration: %d of %d) Creating instance with name %s\n Plan: %+v", i, *c.Client.client.MaxRetries, input.Name, plan))

		instanceInfo, instanceError = c.startInstance(input.Name, plan)
		if instanceError == nil {
			c.client.DebugLogString(fmt.Sprintf("(Iteration: %d of %d) Finished creating instance with name %s\n Info: %+v", i, *c.Client.client.MaxRetries, input.Name, instanceInfo))
			return instanceInfo, nil
		}
	}
	return nil, instanceError
}

func (c *InstancesClient) startInstance(name string, plan LaunchPlanInput) (*InstanceInfo, error) {
	var responseBody LaunchPlanResponse

	if err := c.createResource(&plan, &responseBody); err != nil {
		return nil, err
	}

	if len(responseBody.Instances) == 0 {
		return nil, fmt.Errorf("No instance information returned: %#v", responseBody)
	}

	// Call wait for instance ready now, as creating the instance is an eventually consistent operation
	getInput := &GetInstanceInput{
		Name: name,
		ID:   responseBody.Instances[0].ID,
	}

	if plan.PollInterval == 0 {
		plan.PollInterval = waitForInstanceReadyPollInterval
	}
	if plan.Timeout == 0 {
		plan.Timeout = waitForInstanceReadyTimeout
	}

	// Wait for instance to be ready and return the result
	// Don't have to unqualify any objects, as the GetInstance method will handle that
	instanceInfo, instanceError := c.WaitForInstanceRunning(getInput, plan.PollInterval, plan.Timeout)
	// If the instance enters an error state we need to delete the instance and retry
	if instanceError != nil {
		deleteInput := &DeleteInstanceInput{
			Name: name,
			ID:   responseBody.Instances[0].ID,
		}
		err := c.DeleteInstance(deleteInput)
		if err != nil {
			return nil, fmt.Errorf("Error deleting instance %s: %s", name, err)
		}
		return nil, instanceError
	}
	return instanceInfo, nil
}

// GetInstanceInput specifies the parameters needed to retrieve an instance
type GetInstanceInput struct {
	// The Unqualified Name of this Instance
	// Required
	Name string
	// The Unqualified ID of this Instance
	// Required
	ID string
}

func (g *GetInstanceInput) String() string {
	return fmt.Sprintf(cmpQualifiedName, g.Name, g.ID)
}

// GetInstance retrieves information about an instance.
func (c *InstancesClient) GetInstance(input *GetInstanceInput) (*InstanceInfo, error) {
	if input.ID == "" || input.Name == "" {
		return nil, errors.New("Both instance name and ID need to be specified")
	}

	var responseBody InstanceInfo
	if err := c.getResource(input.String(), &responseBody); err != nil {
		return nil, err
	}

	if responseBody.FQDN == "" {
		return nil, fmt.Errorf("Empty response body when requesting instance %s", input.Name)
	}

	// The returned 'Name' attribute is the fully qualified instance name + "/" + ID
	// Split these out to accurately populate the fields
	nID := strings.Split(c.getUnqualifiedName(responseBody.FQDN), "/")
	responseBody.Name = strings.Join(nID[0:len(nID)-1], "/")
	responseBody.ID = nID[len(nID)-1]

	c.unqualify(&responseBody.VCableID)

	// Unqualify SSH Key names
	sshKeyNames := []string{}
	for _, sshKeyRef := range responseBody.SSHKeys {
		sshKeyNames = append(sshKeyNames, c.getUnqualifiedName(sshKeyRef))
	}
	responseBody.SSHKeys = sshKeyNames

	var networkingErr error
	responseBody.Networking, networkingErr = c.unqualifyNetworking(responseBody.Networking)
	if networkingErr != nil {
		return nil, networkingErr
	}
	responseBody.Storage = c.unqualifyStorage(responseBody.Storage)

	return &responseBody, nil
}

// InstancesInfo specifies a list of instances
type InstancesInfo struct {
	Instances []InstanceInfo `json:"result"`
}

// GetInstanceIDInput specifies the parameters needed to retrieve an instance
type GetInstanceIDInput struct {
	// Name of the instance you want to get
	Name string
}

// GetInstanceFromName loops through all the instances and finds the instance for the given name
// This is needed for orchestration since it doesn't return the id for the instance it creates.
func (c *InstancesClient) GetInstanceFromName(input *GetInstanceIDInput) (*InstanceInfo, error) {
	input.Name = c.getQualifiedName(input.Name)

	var instancesInfo InstancesInfo
	if err := c.getResource(fmt.Sprintf("%s/", c.getUserName()), &instancesInfo); err != nil {
		return nil, err
	}

	for _, i := range instancesInfo.Instances {
		if strings.Contains(i.FQDN, input.Name) {
			if i.Name == "" {
				return nil, fmt.Errorf("Empty response body when requesting instance %s", input.Name)
			}

			// The returned 'Name' attribute is the fully qualified instance name + "/" + ID
			// Split these out to accurately populate the fields
			nID := strings.Split(c.getUnqualifiedName(i.FQDN), "/")
			i.Name = strings.Join(nID[0:len(nID)-1], "/")
			i.ID = nID[len(nID)-1]

			c.unqualify(&i.VCableID)

			// Unqualify SSH Key names
			sshKeyNames := []string{}
			for _, sshKeyRef := range i.SSHKeys {
				sshKeyNames = append(sshKeyNames, c.getUnqualifiedName(sshKeyRef))
			}
			i.SSHKeys = sshKeyNames

			var networkingErr error
			i.Networking, networkingErr = c.unqualifyNetworking(i.Networking)
			if networkingErr != nil {
				return nil, networkingErr
			}
			i.Storage = c.unqualifyStorage(i.Storage)

			return &i, nil
		}
	}

	return nil, fmt.Errorf("Unable to find instance: %q", input.Name)
}

// UpdateInstanceInput specifies the parameters needed to update an instance
type UpdateInstanceInput struct {
	// Name of this instance, generated by the server.
	// Required
	Name string `json:"name"`
	// The desired state of the opc instance. Can only be `running` or `shutdown`
	// Omits if empty.
	// Optional
	DesiredState InstanceDesiredState `json:"desired_state,omitempty"`
	// The ID of the instance
	// Required
	ID string `json:"-"`
	// A list of tags to be supplied to the instance
	// Optional
	Tags []string `json:"tags,omitempty"`
	// Time to wait between polls for instance state
	PollInterval time.Duration `json:"-"`
	// Time to wait for instance to be ready, or shutdown depending on desired state
	Timeout time.Duration `json:"-"`
}

func (g *UpdateInstanceInput) String() string {
	return fmt.Sprintf(cmpQualifiedName, g.Name, g.ID)
}

// UpdateInstance updates an instance with the specified attributes
func (c *InstancesClient) UpdateInstance(input *UpdateInstanceInput) (*InstanceInfo, error) {
	if input.Name == "" || input.ID == "" {
		return nil, errors.New("Both instance name and ID need to be specified")
	}

	input.Name = fmt.Sprintf(cmpQualifiedName, c.getUserName(), input.Name)

	var responseBody InstanceInfo
	if err := c.updateResource(input.String(), input, &responseBody); err != nil {
		return nil, err
	}

	getInput := &GetInstanceInput{
		Name: input.Name,
		ID:   input.ID,
	}

	if input.PollInterval == 0 {
		input.PollInterval = waitForInstanceReadyPollInterval
	}

	if input.Timeout == 0 {
		input.Timeout = waitForInstanceReadyTimeout
	}

	// Wait for the correct instance action depending on the current desired state.
	// If the instance is already running, and the desired state is to be "running", the
	// wait loop will only execute a single time to verify the instance state. Otherwise
	// we wait until the correct action has finalized, either a shutdown or restart, catching
	// any intermittent errors during the process.
	if responseBody.DesiredState == InstanceDesiredRunning {
		return c.WaitForInstanceRunning(getInput, input.PollInterval, input.Timeout)
	}
	return c.WaitForInstanceShutdown(getInput, input.PollInterval, input.Timeout)
}

// DeleteInstanceInput specifies the parameters needed to delete an instance
type DeleteInstanceInput struct {
	// The Unqualified Name of this Instance
	Name string
	// The Unqualified ID of this Instance
	ID string
	// Time to wait between polls to check status
	PollInterval time.Duration
	// Time to wait for instance to be deleted
	Timeout time.Duration
}

func (d *DeleteInstanceInput) String() string {
	return fmt.Sprintf(cmpQualifiedName, d.Name, d.ID)
}

// DeleteInstance deletes an instance.
func (c *InstancesClient) DeleteInstance(input *DeleteInstanceInput) error {
	// Call to delete the instance
	if err := c.deleteResource(input.String()); err != nil {
		return err
	}

	if input.PollInterval == 0 {
		input.PollInterval = waitForInstanceDeletePollInterval
	}
	if input.Timeout == 0 {
		input.Timeout = waitForInstanceDeleteTimeout
	}

	// Wait for instance to be deleted
	return c.WaitForInstanceDeleted(input, input.PollInterval, input.Timeout)
}

// WaitForInstanceRunning waits for an instance to be completely initialized and available.
func (c *InstancesClient) WaitForInstanceRunning(input *GetInstanceInput, pollInterval, timeout time.Duration) (*InstanceInfo, error) {
	var info *InstanceInfo
	var getErr error
	err := c.client.WaitFor("instance to be ready", pollInterval, timeout, func() (bool, error) {
		info, getErr = c.GetInstance(input)
		if getErr != nil {
			return false, getErr
		}
		c.client.DebugLogString(fmt.Sprintf("Instance name is %v, Instance info is %+v", info.Name, info))
		switch s := info.State; s {
		case InstanceError:
			return false, fmt.Errorf("Error initializing instance: %s", info.ErrorReason)
		case InstanceRunning: // Target State
			c.client.DebugLogString("Instance Running")
			return true, nil
		case InstanceQueued:
			c.client.DebugLogString("Instance Queuing")
			return false, nil
		case InstanceInitializing:
			c.client.DebugLogString("Instance Initializing")
			return false, nil
		case InstancePreparing:
			c.client.DebugLogString("Instance Preparing")
			return false, nil
		case InstanceStarting:
			c.client.DebugLogString("Instance Starting")
			return false, nil
		default:
			c.client.DebugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s))
			return false, nil
		}
	})
	return info, err
}

// WaitForInstanceShutdown waits for an instance to be shutdown
func (c *InstancesClient) WaitForInstanceShutdown(input *GetInstanceInput, pollInterval, timeout time.Duration) (*InstanceInfo, error) {
	var info *InstanceInfo
	var getErr error
	err := c.client.WaitFor("instance to be shutdown", pollInterval, timeout, func() (bool, error) {
		info, getErr = c.GetInstance(input)
		if getErr != nil {
			return false, getErr
		}
		switch s := info.State; s {
		case InstanceError:
			return false, fmt.Errorf("Error initializing instance: %s", info.ErrorReason)
		case InstanceRunning:
			c.client.DebugLogString("Instance Running")
			return false, nil
		case InstanceQueued:
			c.client.DebugLogString("Instance Queuing")
			return false, nil
		case InstanceInitializing:
			c.client.DebugLogString("Instance Initializing")
			return false, nil
		case InstancePreparing:
			c.client.DebugLogString("Instance Preparing")
			return false, nil
		case InstanceStarting:
			c.client.DebugLogString("Instance Starting")
			return false, nil
		case InstanceShutdown: // Target State
			c.client.DebugLogString("Instance Shutdown")
			return true, nil
		default:
			c.client.DebugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s))
			return false, nil
		}
	})
	return info, err
}

// WaitForInstanceDeleted waits for an instance to be fully deleted.
func (c *InstancesClient) WaitForInstanceDeleted(input fmt.Stringer, pollInterval, timeout time.Duration) error {
	return c.client.WaitFor("instance to be deleted", pollInterval, timeout, func() (bool, error) {
		var info InstanceInfo
		if err := c.getResource(input.String(), &info); err != nil {
			if client.WasNotFoundError(err) {
				// Instance could not be found, thus deleted
				return true, nil
			}
			// Some other error occurred trying to get instance, exit
			return false, err
		}
		switch s := info.State; s {
		case InstanceError:
			return false, fmt.Errorf("Error stopping instance: %s", info.ErrorReason)
		case InstanceStopping:
			c.client.DebugLogString("Instance stopping")
			return false, nil
		default:
			c.client.DebugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s))
			return false, nil
		}
	})
}

func (c *InstancesClient) qualifyNetworking(info map[string]NetworkingInfo) map[string]NetworkingInfo {
	qualifiedNetworks := map[string]NetworkingInfo{}
	for k, v := range info {
		qfd := v
		sharedNetwork := false
		if v.IPNetwork != "" {
			// Network interface is for an IP Network
			qfd.IPNetwork = c.getQualifiedName(v.IPNetwork)
			sharedNetwork = true
		}
		if v.Vnic != "" {
			qfd.Vnic = c.getQualifiedName(v.Vnic)
		}
		if v.Nat != nil {
			qfd.Nat = c.qualifyNat(v.Nat, sharedNetwork)
		}
		if v.VnicSets != nil {
			qfd.VnicSets = c.getQualifiedList(v.VnicSets)
		}
		if v.SecLists != nil {
			// Network interface is for the shared network
			qfd.SecLists = c.getQualifiedList(v.SecLists)
		}
		qualifiedNetworks[k] = qfd
	}
	return qualifiedNetworks
}

func (c *InstancesClient) unqualifyNetworking(info map[string]NetworkingInfo) (map[string]NetworkingInfo, error) {
	// Unqualify ip network
	var err error
	unqualifiedNetworks := map[string]NetworkingInfo{}
	for k, v := range info {
		unq := v
		if v.IPNetwork != "" {
			unq.IPNetwork = c.getUnqualifiedName(v.IPNetwork)
		}
		if v.Vnic != "" {
			unq.Vnic = c.getUnqualifiedName(v.Vnic)
		}
		if v.Nat != nil && len(v.Nat) > 0 && v.Nat[0] != "null" {
			unq.Nat, err = c.unqualifyNat(v.Nat)
			if err != nil {
				return nil, err
			}
		}
		if v.VnicSets != nil {
			unq.VnicSets = c.getUnqualifiedList(v.VnicSets)
		}
		if v.SecLists != nil {
			unq.SecLists = c.getUnqualifiedList(v.SecLists)
		}
		unqualifiedNetworks[k] = unq
	}
	return unqualifiedNetworks, nil
}

func (c *InstancesClient) qualifyNat(nat []string, shared bool) []string {
	qualifiedNats := []string{}
	for _, v := range nat {
		if strings.HasPrefix(v, "ippool:/oracle") {
			qualifiedNats = append(qualifiedNats, v)
			continue
		}
		prefix := ReservationPrefix
		if shared {
			prefix = ReservationIPPrefix
		}
		qualifiedNats = append(qualifiedNats, fmt.Sprintf("%s:%s", prefix, c.getQualifiedName(v)))
	}
	return qualifiedNats
}

func (c *InstancesClient) unqualifyNat(nat []string) ([]string, error) {
	unQualifiedNats := []string{}
	for _, v := range nat {
		if strings.HasPrefix(v, "ippool:/oracle") {
			unQualifiedNats = append(unQualifiedNats, v)
			continue
		}
		n := strings.Split(v, ":")
		if len(n) < 2 {
			return nil, fmt.Errorf("Error unqualifying NAT: %s", v)
		}
		u := n[1]
		unQualifiedNats = append(unQualifiedNats, c.getUnqualifiedName(u))
	}
	return unQualifiedNats, nil
}

func (c *InstancesClient) unqualifyStorage(attachments []StorageAttachment) []StorageAttachment {
	unqAttachments := []StorageAttachment{}
	for _, v := range attachments {
		if v.StorageVolumeName != "" {
			v.StorageVolumeName = c.getUnqualifiedName(v.StorageVolumeName)
		}
		unqAttachments = append(unqAttachments, v)
	}

	return unqAttachments
}
