package convert

import (
	gogotypes "github.com/gogo/protobuf/types"
	volumetypes "github.com/moby/moby/api/types/volume"
	swarmapi "github.com/moby/swarmkit/v2/api"
)

// VolumeFromGRPC converts a swarmkit api Volume object to a docker api Volume
// object
func VolumeFromGRPC(v *swarmapi.Volume) volumetypes.Volume {
	clusterVolumeSpec := volumetypes.ClusterVolumeSpec{
		Group:                     v.Spec.Group,
		AccessMode:                accessModeFromGRPC(v.Spec.AccessMode),
		AccessibilityRequirements: topologyRequirementFromGRPC(v.Spec.AccessibilityRequirements),
		CapacityRange:             capacityRangeFromGRPC(v.Spec.CapacityRange),
		Secrets:                   volumeSecretsFromGRPC(v.Spec.Secrets),
		Availability:              volumeAvailabilityFromGRPC(v.Spec.Availability),
	}

	clusterVolume := &volumetypes.ClusterVolume{
		ID:            v.ID,
		Spec:          clusterVolumeSpec,
		PublishStatus: volumePublishStatusFromGRPC(v.PublishStatus),
		Info:          volumeInfoFromGRPC(v.VolumeInfo),
	}

	clusterVolume.Version.Index = v.Meta.Version.Index
	clusterVolume.CreatedAt, _ = gogotypes.TimestampFromProto(v.Meta.CreatedAt)
	clusterVolume.UpdatedAt, _ = gogotypes.TimestampFromProto(v.Meta.UpdatedAt)

	return volumetypes.Volume{
		ClusterVolume: clusterVolume,
		CreatedAt:     clusterVolume.CreatedAt.String(),
		Driver:        v.Spec.Driver.Name,
		Labels:        v.Spec.Annotations.Labels,
		Name:          v.Spec.Annotations.Name,
		Options:       v.Spec.Driver.Options,
		Scope:         "global",
	}
}

func volumeSpecToGRPC(spec volumetypes.ClusterVolumeSpec) *swarmapi.VolumeSpec {
	swarmSpec := &swarmapi.VolumeSpec{
		Group: spec.Group,
	}

	if spec.AccessMode != nil {
		swarmSpec.AccessMode = &swarmapi.VolumeAccessMode{}

		switch spec.AccessMode.Scope {
		case volumetypes.ScopeSingleNode:
			swarmSpec.AccessMode.Scope = swarmapi.VolumeScopeSingleNode
		case volumetypes.ScopeMultiNode:
			swarmSpec.AccessMode.Scope = swarmapi.VolumeScopeMultiNode
		}

		switch spec.AccessMode.Sharing {
		case volumetypes.SharingNone:
			swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingNone
		case volumetypes.SharingReadOnly:
			swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingReadOnly
		case volumetypes.SharingOneWriter:
			swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingOneWriter
		case volumetypes.SharingAll:
			swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingAll
		}

		if spec.AccessMode.BlockVolume != nil {
			swarmSpec.AccessMode.AccessType = &swarmapi.VolumeAccessMode_Block{
				Block: &swarmapi.VolumeAccessMode_BlockVolume{},
			}
		}
		if spec.AccessMode.MountVolume != nil {
			swarmSpec.AccessMode.AccessType = &swarmapi.VolumeAccessMode_Mount{
				Mount: &swarmapi.VolumeAccessMode_MountVolume{
					FsType:     spec.AccessMode.MountVolume.FsType,
					MountFlags: spec.AccessMode.MountVolume.MountFlags,
				},
			}
		}
	}

	for _, secret := range spec.Secrets {
		swarmSpec.Secrets = append(swarmSpec.Secrets, &swarmapi.VolumeSecret{
			Key:    secret.Key,
			Secret: secret.Secret,
		})
	}

	if spec.AccessibilityRequirements != nil {
		swarmSpec.AccessibilityRequirements = &swarmapi.TopologyRequirement{}

		for _, top := range spec.AccessibilityRequirements.Requisite {
			swarmSpec.AccessibilityRequirements.Requisite = append(
				swarmSpec.AccessibilityRequirements.Requisite,
				&swarmapi.Topology{
					Segments: top.Segments,
				},
			)
		}

		for _, top := range spec.AccessibilityRequirements.Preferred {
			swarmSpec.AccessibilityRequirements.Preferred = append(
				swarmSpec.AccessibilityRequirements.Preferred,
				&swarmapi.Topology{
					Segments: top.Segments,
				},
			)
		}
	}

	if spec.CapacityRange != nil {
		swarmSpec.CapacityRange = &swarmapi.CapacityRange{
			RequiredBytes: spec.CapacityRange.RequiredBytes,
			LimitBytes:    spec.CapacityRange.LimitBytes,
		}
	}

	// availability is not a pointer, it is a value. if the user does not
	// specify an availability, it will be inferred as the 0-value, which is
	// "active".
	switch spec.Availability {
	case volumetypes.AvailabilityActive:
		swarmSpec.Availability = swarmapi.VolumeAvailabilityActive
	case volumetypes.AvailabilityPause:
		swarmSpec.Availability = swarmapi.VolumeAvailabilityPause
	case volumetypes.AvailabilityDrain:
		swarmSpec.Availability = swarmapi.VolumeAvailabilityDrain
	}

	return swarmSpec
}

// VolumeCreateToGRPC takes a VolumeCreateBody and outputs the matching
// swarmapi VolumeSpec.
func VolumeCreateToGRPC(volume *volumetypes.CreateRequest) *swarmapi.VolumeSpec {
	var swarmSpec *swarmapi.VolumeSpec
	if volume != nil && volume.ClusterVolumeSpec != nil {
		swarmSpec = volumeSpecToGRPC(*volume.ClusterVolumeSpec)
	} else {
		swarmSpec = &swarmapi.VolumeSpec{}
	}

	swarmSpec.Annotations = swarmapi.Annotations{
		Name:   volume.Name,
		Labels: volume.Labels,
	}

	swarmSpec.Driver = &swarmapi.Driver{
		Name:    volume.Driver,
		Options: volume.DriverOpts,
	}

	return swarmSpec
}

func volumeInfoFromGRPC(info *swarmapi.VolumeInfo) *volumetypes.Info {
	if info == nil {
		return nil
	}

	var accessibleTopology []volumetypes.Topology
	if info.AccessibleTopology != nil {
		accessibleTopology = make([]volumetypes.Topology, len(info.AccessibleTopology))
		for i, top := range info.AccessibleTopology {
			accessibleTopology[i] = topologyFromGRPC(top)
		}
	}

	return &volumetypes.Info{
		CapacityBytes:      info.CapacityBytes,
		VolumeContext:      info.VolumeContext,
		VolumeID:           info.VolumeID,
		AccessibleTopology: accessibleTopology,
	}
}

func volumePublishStatusFromGRPC(publishStatus []*swarmapi.VolumePublishStatus) []*volumetypes.PublishStatus {
	if publishStatus == nil {
		return nil
	}

	vps := make([]*volumetypes.PublishStatus, len(publishStatus))
	for i, status := range publishStatus {
		var state volumetypes.PublishState
		switch status.State {
		case swarmapi.VolumePublishStatus_PENDING_PUBLISH:
			state = volumetypes.StatePending
		case swarmapi.VolumePublishStatus_PUBLISHED:
			state = volumetypes.StatePublished
		case swarmapi.VolumePublishStatus_PENDING_NODE_UNPUBLISH:
			state = volumetypes.StatePendingNodeUnpublish
		case swarmapi.VolumePublishStatus_PENDING_UNPUBLISH:
			state = volumetypes.StatePendingUnpublish
		}

		vps[i] = &volumetypes.PublishStatus{
			NodeID:         status.NodeID,
			State:          state,
			PublishContext: status.PublishContext,
		}
	}

	return vps
}

func accessModeFromGRPC(accessMode *swarmapi.VolumeAccessMode) *volumetypes.AccessMode {
	if accessMode == nil {
		return nil
	}

	convertedAccessMode := &volumetypes.AccessMode{}

	switch accessMode.Scope {
	case swarmapi.VolumeScopeSingleNode:
		convertedAccessMode.Scope = volumetypes.ScopeSingleNode
	case swarmapi.VolumeScopeMultiNode:
		convertedAccessMode.Scope = volumetypes.ScopeMultiNode
	}

	switch accessMode.Sharing {
	case swarmapi.VolumeSharingNone:
		convertedAccessMode.Sharing = volumetypes.SharingNone
	case swarmapi.VolumeSharingReadOnly:
		convertedAccessMode.Sharing = volumetypes.SharingReadOnly
	case swarmapi.VolumeSharingOneWriter:
		convertedAccessMode.Sharing = volumetypes.SharingOneWriter
	case swarmapi.VolumeSharingAll:
		convertedAccessMode.Sharing = volumetypes.SharingAll
	}

	if block := accessMode.GetBlock(); block != nil {
		convertedAccessMode.BlockVolume = &volumetypes.TypeBlock{}
	}
	if mount := accessMode.GetMount(); mount != nil {
		convertedAccessMode.MountVolume = &volumetypes.TypeMount{
			FsType:     mount.FsType,
			MountFlags: mount.MountFlags,
		}
	}

	return convertedAccessMode
}

func volumeSecretsFromGRPC(secrets []*swarmapi.VolumeSecret) []volumetypes.Secret {
	if secrets == nil {
		return nil
	}
	convertedSecrets := make([]volumetypes.Secret, len(secrets))
	for i, secret := range secrets {
		convertedSecrets[i] = volumetypes.Secret{
			Key:    secret.Key,
			Secret: secret.Secret,
		}
	}
	return convertedSecrets
}

func topologyRequirementFromGRPC(top *swarmapi.TopologyRequirement) *volumetypes.TopologyRequirement {
	if top == nil {
		return nil
	}

	convertedTop := &volumetypes.TopologyRequirement{}
	if top.Requisite != nil {
		convertedTop.Requisite = make([]volumetypes.Topology, len(top.Requisite))
		for i, req := range top.Requisite {
			convertedTop.Requisite[i] = topologyFromGRPC(req)
		}
	}

	if top.Preferred != nil {
		convertedTop.Preferred = make([]volumetypes.Topology, len(top.Preferred))
		for i, pref := range top.Preferred {
			convertedTop.Preferred[i] = topologyFromGRPC(pref)
		}
	}

	return convertedTop
}

func topologyFromGRPC(top *swarmapi.Topology) volumetypes.Topology {
	if top == nil {
		return volumetypes.Topology{}
	}
	return volumetypes.Topology{
		Segments: top.Segments,
	}
}

func capacityRangeFromGRPC(capacity *swarmapi.CapacityRange) *volumetypes.CapacityRange {
	if capacity == nil {
		return nil
	}

	return &volumetypes.CapacityRange{
		RequiredBytes: capacity.RequiredBytes,
		LimitBytes:    capacity.LimitBytes,
	}
}

func volumeAvailabilityFromGRPC(availability swarmapi.VolumeSpec_VolumeAvailability) volumetypes.Availability {
	switch availability {
	case swarmapi.VolumeAvailabilityActive:
		return volumetypes.AvailabilityActive
	case swarmapi.VolumeAvailabilityPause:
		return volumetypes.AvailabilityPause
	case swarmapi.VolumeAvailabilityDrain:
		return volumetypes.AvailabilityDrain
	default:
		return volumetypes.AvailabilityDrain
	}
}
