package controller

import (
	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/golang/glog"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deployutil "github.com/openshift/origin/pkg/deploy/util"
)

// DeploymentConfigController is responsible for creating a deployment when a DeploymentConfig is
// updated with a new LatestVersion. Any deployment created is correlated to a DeploymentConfig
// by setting the DeploymentConfigLabel on the deployment.
//
// Deployments are represented by ReplicationControllers. The DeploymentConfig used to create the
// ReplicationController is encoded and stored in an annotation on the ReplicationController.
type DeploymentConfigController struct {
	// DeploymentInterface provides access to Deployments.
	DeploymentInterface dccDeploymentInterface
	// NextDeploymentConfig blocks until the next DeploymentConfig is available.
	NextDeploymentConfig func() *deployapi.DeploymentConfig
	// Codec is used to encode DeploymentConfigs which are stored on deployments.
	Codec runtime.Codec
	// Stop is an optional channel that controls when the controller exits.
	Stop <-chan struct{}
}

// dccDeploymentInterface is a small private interface for dealing with Deployments.
type dccDeploymentInterface interface {
	GetDeployment(namespace, name string) (*kapi.ReplicationController, error)
	CreateDeployment(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error)
}

// Process DeploymentConfig events one at a time.
func (c *DeploymentConfigController) Run() {
	go util.Until(c.HandleDeploymentConfig, 0, c.Stop)
}

// Process a single DeploymentConfig event.
func (c *DeploymentConfigController) HandleDeploymentConfig() {
	config := c.NextDeploymentConfig()
	deploy, err := c.shouldDeploy(config)
	if err != nil {
		// TODO: better error handling
		glog.V(2).Infof("Error determining whether to redeploy deploymentConfig %v: %#v", config.Name, err)
		return
	}

	if !deploy {
		glog.V(4).Infof("Won't deploy from config %s", config.Name)
		return
	}

	if deployment, err := deployutil.MakeDeployment(config, c.Codec); err != nil {
		glog.V(2).Infof("Error making deployment from config %s: %v", config.Name, err)
	} else {
		glog.V(2).Infof("Creating new deployment from config %s", config.Name)
		if _, deployErr := c.DeploymentInterface.CreateDeployment(config.Namespace, deployment); deployErr != nil {
			glog.V(2).Infof("Error deploying config %s: %v", config.Name, deployErr)
		}
	}
}

// shouldDeploy returns true if the DeploymentConfig should have a new Deployment created.
func (c *DeploymentConfigController) shouldDeploy(config *deployapi.DeploymentConfig) (bool, error) {
	if config.LatestVersion == 0 {
		glog.V(4).Infof("Shouldn't deploy config %s with LatestVersion=0", config.Name)
		return false, nil
	}

	latestDeploymentID := deployutil.LatestDeploymentIDForConfig(config)
	deployment, err := c.DeploymentInterface.GetDeployment(config.Namespace, latestDeploymentID)

	if err != nil {
		if errors.IsNotFound(err) {
			glog.V(4).Infof("Should deploy config %s because there's no latest deployment", config.Name)
			return true, nil
		}
		glog.V(4).Infof("Shouldn't deploy config %s because of an error looking up latest deployment", config.Name)
		return false, err
	}

	glog.V(4).Infof("Shouldn't deploy because a deployment '%s' already exists for config %s", deployment.Name, config.Name)
	return false, nil
}