package template

import (
	"fmt"
	"regexp"
	"strings"

	kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/golang/glog"

	config "github.com/openshift/origin/pkg/config/api"
	"github.com/openshift/origin/pkg/template/api"
	. "github.com/openshift/origin/pkg/template/generator"
)

var parameterExp = regexp.MustCompile(`\$\{([a-zA-Z0-9\_]+)\}`)

// TemplateProcessor transforms Template objects into Config objects.
type TemplateProcessor struct {
	Generators map[string]Generator
}

// NewTemplateProcessor creates new TemplateProcessor and initializes
// its set of generators.
func NewTemplateProcessor(generators map[string]Generator) *TemplateProcessor {
	return &TemplateProcessor{Generators: generators}
}

// Process transforms Template object into Config object. It generates
// Parameter values using the defined set of generators first, and then it
// substitutes all Parameter expression occurances with their corresponding
// values (currently in the containers' Environment variables only).
func (p *TemplateProcessor) Process(template *api.Template) (*config.Config, error) {
	if err := p.GenerateParameterValues(template); err != nil {
		return nil, err
	}
	if err := p.SubstituteParameters(template); err != nil {
		return nil, err
	}

	config := &config.Config{
		Name:        template.Name,
		Description: template.Description,
		Items:       template.Items,
	}
	config.ID = template.ID
	config.Kind = "Config"
	config.CreationTimestamp = util.Now()
	return config, nil
}

// AddParameter adds new custom parameter to the Template. It overrides
// the existing parameter, if already defined.
func (p *TemplateProcessor) AddParameter(t *api.Template, param api.Parameter) {
	if existing := p.GetParameterByName(t, param.Name); existing != nil {
		*existing = param
	} else {
		t.Parameters = append(t.Parameters, param)
	}
}

// GetParameterByName searches for a Parameter in the Template
// based on it's name.
func (p *TemplateProcessor) GetParameterByName(t *api.Template, name string) *api.Parameter {
	for i, param := range t.Parameters {
		if param.Name == name {
			return &(t.Parameters[i])
		}
	}
	return nil
}

// SubstituteParameters loops over all Environment variables defined for
// all ReplicationController and Pod containers and substitutes all
// Parameter expression occurances with their corresponding values.
//
// Example of Parameter expression:
//   - ${PARAMETER_NAME}
//
// TODO: Implement substitution for more types and fields.
func (p *TemplateProcessor) SubstituteParameters(t *api.Template) error {
	// Make searching for given parameter name/value more effective
	paramMap := make(map[string]string, len(t.Parameters))
	for _, param := range t.Parameters {
		paramMap[param.Name] = param.Value
	}

	for i, item := range t.Items {
		switch obj := item.Object.(type) {
		case *kubeapi.ReplicationController:
			p.substituteParametersInManifest(&obj.DesiredState.PodTemplate.DesiredState.Manifest, paramMap)
			t.Items[i] = runtime.EmbeddedObject{Object: obj}
		case *kubeapi.Pod:
			p.substituteParametersInManifest(&obj.DesiredState.Manifest, paramMap)
			t.Items[i] = runtime.EmbeddedObject{Object: obj}
		default:
			glog.V(1).Infof("Parameter substitution not implemented for resource '%T'.", obj)
		}
	}

	return nil
}

// substituteParametersInManifest is a helper function that iterates
// over the given manifest and substitutes all Parameter expression
// occurances with their corresponding values.
func (p *TemplateProcessor) substituteParametersInManifest(manifest *kubeapi.ContainerManifest, paramMap map[string]string) {
	for i, _ := range manifest.Containers {
		for e, _ := range manifest.Containers[i].Env {
			envValue := &manifest.Containers[i].Env[e].Value
			// Match all parameter expressions found in the given env var
			for _, match := range parameterExp.FindAllStringSubmatch(*envValue, -1) {
				// Substitute expression with its value, if corresponding parameter found
				if len(match) > 1 {
					if paramValue, found := paramMap[match[1]]; found {
						*envValue = strings.Replace(*envValue, match[0], paramValue, 1)
					}
				}
			}
		}
	}
}

// GenerateParameterValues generates Value for each Parameter of the given
// Template that has Expression field specified and doesn't have any
// Value yet.
//
// Examples (Expression => Value):
//   - "test[0-9]{1}x" => "test7x"
//   - "[0-1]{8}" => "01001100"
//   - "0x[A-F0-9]{4}" => "0xB3AF"
//   - "[a-zA-Z0-9]{8}" => "hW4yQU5i"
func (p *TemplateProcessor) GenerateParameterValues(t *api.Template) error {
	for i, _ := range t.Parameters {
		param := &t.Parameters[i]
		if param.Expression != "" && param.Value == "" {
			generator, ok := p.Generators["expression"]
			if !ok {
				return fmt.Errorf("Can't find expression generator.")
			}
			value, err := generator.GenerateValue(param.Expression)
			if err != nil {
				return err
			}
			param.Value, ok = value.(string)
			if !ok {
				return fmt.Errorf("Can't convert the generated value %v to string.", value)
			}
		}
	}
	return nil
}