package controller

import (
	"github.com/golang/glog"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"

	deployapi "github.com/openshift/origin/pkg/deploy/api"
)

// BasicDeploymentController implements the DeploymentStrategyTypeBasic deployment strategy. Its behavior
// is to create new replication controllers as defined on a Deployment, and delete any previously existing
// replication controllers for the same DeploymentConfig associated with the deployment.
type BasicDeploymentController struct {
	DeploymentUpdater           bdcDeploymentUpdater
	ReplicationControllerClient bdcReplicationControllerClient
	NextDeployment              func() *deployapi.Deployment
}

type bdcDeploymentUpdater interface {
	UpdateDeployment(ctx kapi.Context, deployment *deployapi.Deployment) (*deployapi.Deployment, error)
}

type bdcReplicationControllerClient interface {
	ListReplicationControllers(ctx kapi.Context, selector labels.Selector) (*kapi.ReplicationControllerList, error)
	GetReplicationController(ctx kapi.Context, id string) (*kapi.ReplicationController, error)
	CreateReplicationController(ctx kapi.Context, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error)
	UpdateReplicationController(ctx kapi.Context, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error)
	DeleteReplicationController(ctx kapi.Context, id string) error
}

func (dc *BasicDeploymentController) Run() {
	go util.Forever(func() { dc.HandleDeployment() }, 0)
}

// HandleDeployment executes a single Deployment. It's assumed that the strategy of the deployment is
// DeploymentStrategyTypeBasic.
func (dc *BasicDeploymentController) HandleDeployment() error {
	deployment := dc.NextDeployment()

	if deployment.Strategy.Type != deployapi.DeploymentStrategyTypeBasic {
		glog.V(4).Infof("Ignoring deployment %s due to incompatible strategy type %s", deployment.ID, deployment.Strategy)
		return nil
	}

	ctx := kapi.WithNamespace(kapi.NewContext(), deployment.Namespace)

	nextStatus := deployment.Status
	switch deployment.Status {
	case deployapi.DeploymentStatusNew:
		nextStatus = dc.handleNew(ctx, deployment)
	}

	// persist any status change
	if deployment.Status != nextStatus {
		deployment.Status = nextStatus
		glog.V(4).Infof("Saving deployment %v status: %v", deployment.ID, deployment.Status)
		if _, err := dc.DeploymentUpdater.UpdateDeployment(ctx, deployment); err != nil {
			glog.V(2).Infof("Received error while saving deployment %v: %v", deployment.ID, err)
			return err
		}
	}

	return nil
}

func (dc *BasicDeploymentController) handleNew(ctx kapi.Context, deployment *deployapi.Deployment) deployapi.DeploymentStatus {
	controllers := &kapi.ReplicationControllerList{}
	var err error

	configID, hasConfigID := deployment.Labels[deployapi.DeploymentConfigLabel]
	if hasConfigID {
		selector, _ := labels.ParseSelector(deployapi.DeploymentConfigLabel + "=" + configID)
		controllers, err = dc.ReplicationControllerClient.ListReplicationControllers(ctx, selector)
		if err != nil {
			glog.V(2).Infof("Unable to get list of replication controllers for previous deploymentConfig %s: %v\n", configID, err)
			return deployapi.DeploymentStatusFailed
		}
	}

	controller := &kapi.ReplicationController{
		DesiredState: deployment.ControllerTemplate,
		Labels:       map[string]string{deployapi.DeploymentConfigLabel: configID, "deployment": deployment.ID},
	}

	if controller.DesiredState.PodTemplate.Labels == nil {
		controller.DesiredState.PodTemplate.Labels = make(map[string]string)
	}

	controller.DesiredState.PodTemplate.Labels[deployapi.DeploymentConfigLabel] = configID
	controller.DesiredState.PodTemplate.Labels["deployment"] = deployment.ID

	glog.V(2).Infof("Creating replicationController for deployment %s", deployment.ID)
	if _, err := dc.ReplicationControllerClient.CreateReplicationController(ctx, controller); err != nil {
		glog.V(2).Infof("An error occurred creating the replication controller for deployment %s: %v", deployment.ID, err)
		return deployapi.DeploymentStatusFailed
	}

	allProcessed := true
	// For this simple deploy, remove previous replication controllers
	for _, rc := range controllers.Items {
		configID, _ := deployment.Labels[deployapi.DeploymentConfigLabel]
		glog.V(2).Infof("Stopping replication controller for previous deploymentConfig %s: %v", configID, rc.ID)

		controller, err := dc.ReplicationControllerClient.GetReplicationController(ctx, rc.ID)
		if err != nil {
			glog.V(2).Infof("Unable to get replication controller %s for previous deploymentConfig %s: %#v\n", rc.ID, configID, err)
			allProcessed = false
			continue
		}

		controller.DesiredState.Replicas = 0
		glog.V(2).Infof("Settings Replicas=0 for replicationController %s for previous deploymentConfig %s", rc.ID, configID)
		if _, err := dc.ReplicationControllerClient.UpdateReplicationController(ctx, controller); err != nil {
			glog.V(2).Infof("Unable to stop replication controller %s for previous deploymentConfig %s: %#v\n", rc.ID, configID, err)
			allProcessed = false
			continue
		}
	}

	for _, rc := range controllers.Items {
		configID, _ := deployment.Labels[deployapi.DeploymentConfigLabel]
		glog.V(2).Infof("Deleting replication controller %s for previous deploymentConfig %s", rc.ID, configID)
		err := dc.ReplicationControllerClient.DeleteReplicationController(ctx, rc.ID)
		if err != nil {
			glog.V(2).Infof("Unable to remove replication controller %s for previous deploymentConfig %s:%#v\n", rc.ID, configID, err)
			allProcessed = false
			continue
		}
	}

	if allProcessed {
		return deployapi.DeploymentStatusComplete
	}

	return deployapi.DeploymentStatusFailed
}