package config

import (
	"fmt"
	"path"

	"github.com/Sirupsen/logrus"
	yaml "github.com/cloudfoundry-incubator/candiedyaml"
	"github.com/docker/libcompose/utils"
)

// MergeServicesV2 merges a v2 compose file into an existing set of service configs
func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte, options *ParseOptions) (map[string]*ServiceConfig, error) {
	var config Config
	if err := yaml.Unmarshal(bytes, &config); err != nil {
		return nil, err
	}

	datas := config.Services

	if options.Interpolate {
		if err := Interpolate(environmentLookup, &datas); err != nil {
			return nil, err
		}
	}

	if options.Preprocess != nil {
		var err error
		datas, err = options.Preprocess(datas)
		if err != nil {
			return nil, err
		}
	}

	for name, data := range datas {
		data, err := parseV2(resourceLookup, environmentLookup, file, data, datas, options)
		if err != nil {
			logrus.Errorf("Failed to parse service %s: %v", name, err)
			return nil, err
		}

		if serviceConfig, ok := existingServices.Get(name); ok {
			var rawExistingService RawService
			if err := utils.Convert(serviceConfig, &rawExistingService); err != nil {
				return nil, err
			}

			data = mergeConfig(rawExistingService, data)
		}

		datas[name] = data
	}

	serviceConfigs := make(map[string]*ServiceConfig)
	if err := utils.Convert(datas, &serviceConfigs); err != nil {
		return nil, err
	}

	return serviceConfigs, nil
}

// ParseVolumes parses volumes in a compose file
func ParseVolumes(bytes []byte) (map[string]*VolumeConfig, error) {
	volumeConfigs := make(map[string]*VolumeConfig)

	var config Config
	if err := yaml.Unmarshal(bytes, &config); err != nil {
		return nil, err
	}

	if err := utils.Convert(config.Volumes, &volumeConfigs); err != nil {
		return nil, err
	}

	return volumeConfigs, nil
}

// ParseNetworks parses networks in a compose file
func ParseNetworks(bytes []byte) (map[string]*NetworkConfig, error) {
	networkConfigs := make(map[string]*NetworkConfig)

	var config Config
	if err := yaml.Unmarshal(bytes, &config); err != nil {
		return nil, err
	}

	if err := utils.Convert(config.Networks, &networkConfigs); err != nil {
		return nil, err
	}

	return networkConfigs, nil
}

func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap, options *ParseOptions) (RawService, error) {
	serviceData, err := readEnvFile(resourceLookup, inFile, serviceData)
	if err != nil {
		return nil, err
	}

	serviceData = resolveContextV2(inFile, serviceData)

	value, ok := serviceData["extends"]
	if !ok {
		return serviceData, nil
	}

	mapValue, ok := value.(map[interface{}]interface{})
	if !ok {
		return serviceData, nil
	}

	if resourceLookup == nil {
		return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile)
	}

	file := asString(mapValue["file"])
	service := asString(mapValue["service"])

	if service == "" {
		return serviceData, nil
	}

	var baseService RawService

	if file == "" {
		if serviceData, ok := datas[service]; ok {
			baseService, err = parseV2(resourceLookup, environmentLookup, inFile, serviceData, datas, options)
		} else {
			return nil, fmt.Errorf("Failed to find service %s to extend", service)
		}
	} else {
		bytes, resolved, err := resourceLookup.Lookup(file, inFile)
		if err != nil {
			logrus.Errorf("Failed to lookup file %s: %v", file, err)
			return nil, err
		}

		var config Config
		if err := yaml.Unmarshal(bytes, &config); err != nil {
			return nil, err
		}

		baseRawServices := config.Services

		if options.Interpolate {
			err = Interpolate(environmentLookup, &baseRawServices)
			if err != nil {
				return nil, err
			}
		}

		baseService, ok = baseRawServices[service]
		if !ok {
			return nil, fmt.Errorf("Failed to find service %s in file %s", service, file)
		}

		baseService, err = parseV2(resourceLookup, environmentLookup, resolved, baseService, baseRawServices, options)
	}

	if err != nil {
		return nil, err
	}

	baseService = clone(baseService)

	logrus.Debugf("Merging %#v, %#v", baseService, serviceData)

	for _, k := range noMerge {
		if _, ok := baseService[k]; ok {
			source := file
			if source == "" {
				source = inFile
			}
			return nil, fmt.Errorf("Cannot extend service '%s' in %s: services with '%s' cannot be extended", service, source, k)
		}
	}

	baseService = mergeConfig(baseService, serviceData)

	logrus.Debugf("Merged result %#v", baseService)

	return baseService, nil
}

func resolveContextV2(inFile string, serviceData RawService) RawService {
	if _, ok := serviceData["build"]; !ok {
		return serviceData
	}
	build := serviceData["build"].(map[interface{}]interface{})
	context := asString(build["context"])
	if context == "" {
		return serviceData
	}

	if IsValidRemote(context) {
		return serviceData
	}

	current := path.Dir(inFile)

	if context == "." {
		context = current
	} else {
		current = path.Join(current, context)
	}

	build["context"] = current

	return serviceData
}
