package prune

import (
	"fmt"
	"time"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/client/cache"

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

// DeploymentByDeploymentConfigIndexFunc indexes Deployment items by their associated DeploymentConfig, if none, index with key "orphan"
func DeploymentByDeploymentConfigIndexFunc(obj interface{}) ([]string, error) {
	controller, ok := obj.(*kapi.ReplicationController)
	if !ok {
		return nil, fmt.Errorf("not a replication controller: %v", obj)
	}
	name := deployutil.DeploymentConfigNameFor(controller)
	if len(name) == 0 {
		return []string{"orphan"}, nil
	}
	return []string{controller.Namespace + "/" + name}, nil
}

// Filter filters the set of objects
type Filter interface {
	Filter(items []*kapi.ReplicationController) []*kapi.ReplicationController
}

// andFilter ands a set of predicate functions to know if it should be included in the return set
type andFilter struct {
	filterPredicates []FilterPredicate
}

// Filter ands the set of predicates evaluated against each item to make a filtered set
func (a *andFilter) Filter(items []*kapi.ReplicationController) []*kapi.ReplicationController {
	results := []*kapi.ReplicationController{}
	for _, item := range items {
		include := true
		for _, filterPredicate := range a.filterPredicates {
			include = include && filterPredicate(item)
		}
		if include {
			results = append(results, item)
		}
	}
	return results
}

// FilterPredicate is a function that returns true if the object should be included in the filtered set
type FilterPredicate func(item *kapi.ReplicationController) bool

// NewFilterBeforePredicate is a function that returns true if the build was created before the current time minus specified duration
func NewFilterBeforePredicate(d time.Duration) FilterPredicate {
	now := unversioned.Now()
	before := unversioned.NewTime(now.Time.Add(-1 * d))
	return func(item *kapi.ReplicationController) bool {
		return item.CreationTimestamp.Before(before)
	}
}

// FilterDeploymentsPredicate is a function that returns true if the replication controller is associated with a DeploymentConfig
func FilterDeploymentsPredicate(item *kapi.ReplicationController) bool {
	return len(deployutil.DeploymentConfigNameFor(item)) > 0
}

// FilterZeroReplicaSize is a function that returns true if the replication controller size is 0
func FilterZeroReplicaSize(item *kapi.ReplicationController) bool {
	return item.Spec.Replicas == 0 && item.Status.Replicas == 0
}

// DataSet provides functions for working with deployment data
type DataSet interface {
	GetDeploymentConfig(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, bool, error)
	ListDeploymentConfigs() ([]*deployapi.DeploymentConfig, error)
	ListDeployments() ([]*kapi.ReplicationController, error)
	ListDeploymentsByDeploymentConfig(config *deployapi.DeploymentConfig) ([]*kapi.ReplicationController, error)
}

type dataSet struct {
	deploymentConfigStore cache.Store
	deploymentIndexer     cache.Indexer
}

// NewDataSet returns a DataSet over the specified items
func NewDataSet(deploymentConfigs []*deployapi.DeploymentConfig, deployments []*kapi.ReplicationController) DataSet {
	deploymentConfigStore := cache.NewStore(cache.MetaNamespaceKeyFunc)
	for _, deploymentConfig := range deploymentConfigs {
		deploymentConfigStore.Add(deploymentConfig)
	}

	deploymentIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{
		"deploymentConfig": DeploymentByDeploymentConfigIndexFunc,
	})
	for _, deployment := range deployments {
		deploymentIndexer.Add(deployment)
	}

	return &dataSet{
		deploymentConfigStore: deploymentConfigStore,
		deploymentIndexer:     deploymentIndexer,
	}
}

// GetDeploymentConfig gets the configuration for the given deployment
func (d *dataSet) GetDeploymentConfig(controller *kapi.ReplicationController) (*deployapi.DeploymentConfig, bool, error) {
	name := deployutil.DeploymentConfigNameFor(controller)
	if len(name) == 0 {
		return nil, false, nil
	}

	var deploymentConfig *deployapi.DeploymentConfig
	key := &deployapi.DeploymentConfig{ObjectMeta: kapi.ObjectMeta{Name: name, Namespace: controller.Namespace}}
	item, exists, err := d.deploymentConfigStore.Get(key)
	if exists {
		deploymentConfig = item.(*deployapi.DeploymentConfig)
	}
	return deploymentConfig, exists, err
}

// ListDeploymentConfigs returns a list of DeploymentConfigs
func (d *dataSet) ListDeploymentConfigs() ([]*deployapi.DeploymentConfig, error) {
	results := []*deployapi.DeploymentConfig{}
	for _, item := range d.deploymentConfigStore.List() {
		results = append(results, item.(*deployapi.DeploymentConfig))
	}
	return results, nil
}

// ListDeployments returns a list of deployments
func (d *dataSet) ListDeployments() ([]*kapi.ReplicationController, error) {
	results := []*kapi.ReplicationController{}
	for _, item := range d.deploymentIndexer.List() {
		results = append(results, item.(*kapi.ReplicationController))
	}
	return results, nil
}

// ListDeploymentsByDeploymentConfig returns a list of deployments for the provided configuration
func (d *dataSet) ListDeploymentsByDeploymentConfig(deploymentConfig *deployapi.DeploymentConfig) ([]*kapi.ReplicationController, error) {
	results := []*kapi.ReplicationController{}
	key := &kapi.ReplicationController{
		ObjectMeta: kapi.ObjectMeta{
			Namespace:   deploymentConfig.Namespace,
			Annotations: map[string]string{deployapi.DeploymentConfigAnnotation: deploymentConfig.Name},
		},
	}
	items, err := d.deploymentIndexer.Index("deploymentConfig", key)
	if err != nil {
		return nil, err
	}
	for _, item := range items {
		results = append(results, item.(*kapi.ReplicationController))
	}
	return results, nil
}