/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package deployers

import (
	"encoding/json"
	"fmt"
	"github.com/apache/openwhisk-wskdeploy/webaction"
	"net/http"
	"path"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/apache/openwhisk-client-go/whisk"
	"github.com/apache/openwhisk-wskdeploy/dependencies"
	"github.com/apache/openwhisk-wskdeploy/parsers"
	"github.com/apache/openwhisk-wskdeploy/utils"
	"github.com/apache/openwhisk-wskdeploy/wskderrors"
	"github.com/apache/openwhisk-wskdeploy/wski18n"
	"github.com/apache/openwhisk-wskdeploy/wskprint"
)

const (
	CONFLICT_MESSAGE = "Concurrent modification to resource detected"
	CONFLICT_CODE    = 153
	DEFAULT_ATTEMPTS = 3
	DEFAULT_INTERVAL = 1 * time.Second
)

type DeploymentProject struct {
	Packages          map[string]*DeploymentPackage
	Triggers          map[string]*whisk.Trigger
	Rules             map[string]*whisk.Rule
	Apis              map[string]*whisk.ApiCreateRequest
	ApiOptions        map[string]*whisk.ApiCreateRequestOptions
	SwaggerApi        *whisk.ApiCreateRequest
	SwaggerApiOptions *whisk.ApiCreateRequestOptions
}

func NewDeploymentProject() *DeploymentProject {
	var dep DeploymentProject
	dep.Packages = make(map[string]*DeploymentPackage)
	dep.Triggers = make(map[string]*whisk.Trigger)
	dep.Rules = make(map[string]*whisk.Rule)
	dep.Apis = make(map[string]*whisk.ApiCreateRequest)
	dep.ApiOptions = make(map[string]*whisk.ApiCreateRequestOptions)
	return &dep
}

type DeploymentPackage struct {
	Package      *whisk.Package
	Dependencies map[string]dependencies.DependencyRecord
	Actions      map[string]utils.ActionRecord
	Sequences    map[string]utils.ActionRecord
	Inputs       parsers.PackageInputs
}

func NewDeploymentPackage() *DeploymentPackage {
	var dep DeploymentPackage
	dep.Dependencies = make(map[string]dependencies.DependencyRecord)
	dep.Actions = make(map[string]utils.ActionRecord)
	dep.Sequences = make(map[string]utils.ActionRecord)
	dep.Inputs = parsers.PackageInputs{}
	return &dep
}

// ServiceDeployer defines a prototype service deployer.
// It should do the following:
//   1. Collect information from the manifest file (if any)
//   2. Collect information from the deployment file (if any)
//   3. Collect information about the source code files in the working directory
//   4. Create a deployment plan to create OpenWhisk service
type ServiceDeployer struct {
	ProjectName       string
	ProjectInputs     map[string]parsers.Parameter
	Deployment        *DeploymentProject
	Client            *whisk.Client
	mt                sync.RWMutex
	Preview           bool
	Report            bool
	ManifestPath      string
	ProjectPath       string
	DeploymentPath    string
	ClientConfig      *whisk.Config
	DependencyMaster  map[string]dependencies.DependencyRecord
	ManagedAnnotation whisk.KeyValue
}

// NewServiceDeployer is a Factory to create a new ServiceDeployer
func NewServiceDeployer() *ServiceDeployer {
	var dep ServiceDeployer
	dep.Deployment = NewDeploymentProject()
	dep.Preview = true
	dep.DependencyMaster = make(map[string]dependencies.DependencyRecord)
	dep.ProjectInputs = make(map[string]parsers.Parameter, 0)
	return &dep
}

// Check if the manifest yaml could be parsed by Manifest Parser.
// Check if the deployment yaml could be parsed by Manifest Parser.
func (deployer *ServiceDeployer) Check() {
	ps := parsers.NewYAMLParser()
	if utils.FileExists(deployer.DeploymentPath) {
		ps.ParseDeployment(deployer.DeploymentPath)
	}
	ps.ParseManifest(deployer.ManifestPath)
	// add more schema check or manifest/deployment consistency checks here if
	// necessary
}

func (deployer *ServiceDeployer) setProjectInputs(manifest *parsers.YAML) error {
	for parameterName, param := range manifest.Project.Inputs {
		p, err := parsers.ResolveParameter(parameterName, &param, manifest.Filepath)
		if err != nil {
			return err
		}
		param.Value = p
		deployer.ProjectInputs[parameterName] = param
	}
	return nil
}

func (deployer *ServiceDeployer) ConstructDeploymentPlan() error {

	var manifestReader = NewManifestReader(deployer)
	manifestReader.IsUndeploy = false
	var err error
	manifest, manifestParser, err := manifestReader.ParseManifest()
	if err != nil {
		return err
	}

	deployer.ProjectName = utils.Flags.ProjectName
	if deployer.ProjectName == "" {
		deployer.ProjectName = manifest.GetProject().Name
	} else {
		warningString := wski18n.T(
			wski18n.ID_WARN_PROJECT_NAME_OVERRIDDEN,
			map[string]interface{}{
				wski18n.KEY_PROJECT: deployer.ProjectName})
		wskprint.PrintOpenWhiskWarning(warningString)
	}

	err = deployer.setProjectInputs(manifest)
	if err != nil {
		return err
	}

	// Generate Managed Annotations if its marked as a Managed Deployment
	// Managed deployments are the ones when OpenWhisk entities are deployed with command line flag --managed.
	// Which results in a hidden annotation in every OpenWhisk entity in manifest file.
	if utils.Flags.Managed || utils.Flags.Sync {
		// OpenWhisk entities are annotated with Project Name and therefore
		// Project Name in manifest/deployment file is mandatory for managed deployments
		if deployer.ProjectName == "" {
			errmsg := wski18n.T(wski18n.ID_ERR_KEY_MISSING_X_key_X,
				map[string]interface{}{wski18n.KEY_KEY: wski18n.NAME_PROJECT})

			return wskderrors.NewYAMLFileFormatError(manifest.Filepath, errmsg)
		}
		// Every OpenWhisk entity in the manifest file will be annotated with:
		//managed: '{"__OW__PROJECT__NAME": <name>, "__OW__PROJECT_HASH": <hash>, "__OW__FILE": <path>}'
		deployer.ManagedAnnotation, err = utils.GenerateManagedAnnotation(deployer.ProjectName, manifest.Filepath)
		if err != nil {
			return wskderrors.NewYAMLFileFormatError(manifest.Filepath, err.Error())
		}
	}

	err = manifestReader.InitPackages(manifestParser, manifest, deployer.ManagedAnnotation)
	if err != nil {
		return err
	}

	projectName := ""
	if len(manifest.GetProject().Packages) != 0 {
		projectName = manifest.GetProject().Name
	}

	// process deployment file
	var deploymentReader = NewDeploymentReader(deployer)
	if utils.FileExists(deployer.DeploymentPath) {
		err = deploymentReader.HandleYaml()
		if err != nil {
			return err
		}

		// compare the name of the project
		if len(deploymentReader.DeploymentDescriptor.GetProject().Packages) != 0 && len(projectName) != 0 {
			projectNameDeploy := deploymentReader.DeploymentDescriptor.GetProject().Name
			if projectNameDeploy != projectName {
				errorString := wski18n.T(wski18n.ID_ERR_NAME_MISMATCH_X_key_X_dname_X_dpath_X_mname_X_moath_X,
					map[string]interface{}{
						wski18n.KEY_KEY:             parsers.YAML_KEY_PROJECT,
						wski18n.KEY_DEPLOYMENT_NAME: projectNameDeploy,
						wski18n.KEY_DEPLOYMENT_PATH: deployer.DeploymentPath,
						wski18n.KEY_MANIFEST_NAME:   projectName,
						wski18n.KEY_MANIFEST_PATH:   deployer.ManifestPath})
				return wskderrors.NewYAMLFileFormatError(manifest.Filepath, errorString)
			}
		}
	}

	// overwrite package inputs based on command line parameters
	// overwrite package inputs with the values from command line --param and/or --param-file in order
	err = deployer.UpdatePackageInputs()
	if err != nil {
		return err
	}

	// process manifest file
	err = manifestReader.HandleYaml(manifestParser, manifest, deployer.ManagedAnnotation)
	if err != nil {
		return err
	}

	// process deployment file
	if utils.FileExists(deployer.DeploymentPath) {
		if err := deploymentReader.BindAssets(); err != nil {
			return err
		}
	}

	return err
}

func (deployer *ServiceDeployer) ConstructUnDeploymentPlan() (*DeploymentProject, error) {

	var manifestReader = NewManifestReader(deployer)
	manifestReader.IsUndeploy = true
	var err error
	manifest, manifestParser, err := manifestReader.ParseManifest()
	if err != nil {
		return deployer.Deployment, err
	}

	manifestReader.InitPackages(manifestParser, manifest, whisk.KeyValue{})

	// process manifest file
	err = manifestReader.HandleYaml(manifestParser, manifest, whisk.KeyValue{})
	if err != nil {
		return deployer.Deployment, err
	}

	projectName := ""
	if len(manifest.GetProject().Packages) != 0 {
		projectName = manifest.GetProject().Name
	}

	// process deployment file
	if utils.FileExists(deployer.DeploymentPath) {
		var deploymentReader = NewDeploymentReader(deployer)
		err = deploymentReader.HandleYaml()
		if err != nil {
			return deployer.Deployment, err
		}

		// compare the name of the project
		if len(deploymentReader.DeploymentDescriptor.GetProject().Packages) != 0 && len(projectName) != 0 {
			projectNameDeploy := deploymentReader.DeploymentDescriptor.GetProject().Name
			if projectNameDeploy != projectName {
				errorString := wski18n.T(wski18n.ID_ERR_NAME_MISMATCH_X_key_X_dname_X_dpath_X_mname_X_moath_X,
					map[string]interface{}{
						wski18n.KEY_KEY:             parsers.YAML_KEY_PROJECT,
						wski18n.KEY_DEPLOYMENT_NAME: projectNameDeploy,
						wski18n.KEY_DEPLOYMENT_PATH: deployer.DeploymentPath,
						wski18n.KEY_MANIFEST_NAME:   projectName,
						wski18n.KEY_MANIFEST_PATH:   deployer.ManifestPath})
				return deployer.Deployment, wskderrors.NewYAMLFileFormatError(manifest.Filepath, errorString)
			}
		}

		if err := deploymentReader.BindAssets(); err != nil {
			return deployer.Deployment, err
		}
	}

	verifiedPlan := deployer.Deployment

	return verifiedPlan, err
}

// Use reflect util to deploy everything in this service deployer
// TODO(TBD): according to some planning?
func (deployer *ServiceDeployer) Deploy() error {

	if deployer.Preview {
		deployer.printDeploymentAssets(deployer.Deployment)
		return nil
	}

	if deployer.Report {
		deployer.reportInputs()
		return nil
	}

	if err := deployer.deployAssets(); err != nil {
		wskprint.PrintOpenWhiskError(wski18n.T(wski18n.ID_MSG_DEPLOYMENT_FAILED))
		return err
	}

	wskprint.PrintOpenWhiskSuccess(wski18n.T(wski18n.T(wski18n.ID_MSG_DEPLOYMENT_SUCCEEDED)))
	return nil
}

func (deployer *ServiceDeployer) deployAssets() error {

	if err := deployer.DeployPackages(); err != nil {
		return err
	}

	if err := deployer.DeployDependencies(); err != nil {
		return err
	}

	if err := deployer.DeployActions(); err != nil {
		return err
	}

	if err := deployer.DeploySequences(); err != nil {
		return err
	}

	if err := deployer.DeployTriggers(); err != nil {
		return err
	}

	if err := deployer.DeployRules(); err != nil {
		return err
	}

	if err := deployer.DeployApis(); err != nil {
		return err
	}

	// During managed deployments, after deploying list of entities in a project
	// refresh previously deployed project entities, delete the assets which is no longer part of the project
	// i.e. in a subsequent managed deployment of the same project minus few OpenWhisk entities
	// from the manifest file must result in undeployment of those deleted entities
	if utils.Flags.Managed || utils.Flags.Sync {
		if err := deployer.RefreshManagedEntities(deployer.ManagedAnnotation); err != nil {
			errString := wski18n.T(wski18n.ID_MSG_MANAGED_UNDEPLOYMENT_FAILED)
			whisk.Debug(whisk.DbgError, errString)
			return err
		}
	}

	return nil
}

func (deployer *ServiceDeployer) DeployDependencies() error {
	for _, pack := range deployer.Deployment.Packages {
		for depName, depRecord := range pack.Dependencies {
			output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYING_X_name_X,
				map[string]interface{}{wski18n.KEY_NAME: depName})
			whisk.Debug(whisk.DbgInfo, output)

			if depRecord.IsBinding {
				bindingPackage := new(whisk.BindingPackage)
				bindingPackage.Namespace = pack.Package.Namespace
				bindingPackage.Name = depName
				pub := false
				bindingPackage.Publish = &pub

				qName, err := utils.ParseQualifiedName(depRecord.Location, pack.Package.Namespace)
				if err != nil {
					return err
				}
				bindingPackage.Binding = whisk.Binding{qName.Namespace, qName.EntityName}

				bindingPackage.Parameters = depRecord.Parameters
				bindingPackage.Annotations = depRecord.Annotations

				error := deployer.createBinding(bindingPackage)
				if error != nil {
					return error
				} else {
					output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYMENT_SUCCESS_X_name_X,
						map[string]interface{}{wski18n.KEY_NAME: depName})
					whisk.Debug(whisk.DbgInfo, output)
				}

			} else {
				depServiceDeployer, err := deployer.getDependentDeployer(depName, depRecord)
				if err != nil {
					return err
				}

				err = depServiceDeployer.ConstructDeploymentPlan()
				if err != nil {
					return err
				}

				dependentPackages := []string{}
				for k := range depServiceDeployer.Deployment.Packages {
					dependentPackages = append(dependentPackages, k)
				}

				if len(dependentPackages) > 1 {
					// TODO(799) i18n
					errMessage := "GitHub dependency " + depName + " has multiple packages in manifest file: " +
						strings.Join(dependentPackages, ", ") + ". " +
						"One GitHub dependency can only be associated with single package in manifest file." +
						"There is no way to reference actions from multiple packages of any GitHub dependencies."
					return wskderrors.NewYAMLFileFormatError(deployer.ManifestPath, errMessage)
				}

				if err := depServiceDeployer.deployAssets(); err != nil {
					errString := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYMENT_FAILURE_X_name_X,
						map[string]interface{}{wski18n.KEY_NAME: depName})
					wskprint.PrintOpenWhiskError(errString)
					return err
				}

				// if the dependency name in the original package
				// is different from the package name in the manifest
				// file of dependent github repo, create a binding to the origin package
				if ok := depServiceDeployer.Deployment.Packages[depName]; ok == nil {
					bindingPackage := new(whisk.BindingPackage)
					bindingPackage.Namespace = pack.Package.Namespace
					bindingPackage.Name = depName
					pub := false
					bindingPackage.Publish = &pub

					qName, err := utils.ParseQualifiedName(dependentPackages[0], depServiceDeployer.Deployment.Packages[dependentPackages[0]].Package.Namespace)
					if err != nil {
						return err
					}

					bindingPackage.Binding = whisk.Binding{qName.Namespace, qName.EntityName}

					bindingPackage.Parameters = depRecord.Parameters
					bindingPackage.Annotations = depRecord.Annotations

					err = deployer.createBinding(bindingPackage)
					if err != nil {
						return err
					} else {
						output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYMENT_SUCCESS_X_name_X,
							map[string]interface{}{wski18n.KEY_NAME: depName})
						whisk.Debug(whisk.DbgInfo, output)
					}
				}
			}
		}
	}

	return nil
}

// TODO() display "update" | "synced" messages pre/post
func (deployer *ServiceDeployer) RefreshManagedEntities(maValue whisk.KeyValue) error {

	ma := maValue.Value.(map[string]interface{})
	if err := deployer.RefreshManagedTriggers(ma); err != nil {
		return err
	}

	if err := deployer.RefreshManagedRules(ma); err != nil {
		return err
	}

	if err := deployer.RefreshManagedPackages(ma); err != nil {
		return err
	}

	if err := deployer.RefreshManagedPackagesWithDependencies(ma); err != nil {
		return err
	}
	return nil

}

// TODO() display "update" | "synced" messages pre/post
func (deployer *ServiceDeployer) RefreshManagedActions(packageName string, ma map[string]interface{}) error {
	options := whisk.ActionListOptions{}
	// get a list of actions in your namespace
	actions, _, err := deployer.Client.Actions.List(packageName, &options)
	if err != nil {
		return err
	}
	// iterate over list of actions to find an action with managed annotations
	// check if "managed" annotation is attached to an action
	for _, action := range actions {
		// an annotation with "managed" key indicates that an action was deployed as part of managed deployment
		// if such annotation exists, check if it belongs to the current managed deployment
		// this action has attached managed annotations
		if a := action.Annotations.GetValue(utils.MANAGED); a != nil {
			// decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH
			aa := a.(map[string]interface{})
			// we have found an action which was earlier part of the current project
			// and this action was deployed as part of managed deployment and now
			// must be undeployed as its not part of the project anymore
			// The annotation with same project name but different project hash indicates
			// that this action is deleted from the project in manifest file
			if aa[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && aa[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] {
				actionName := strings.Join([]string{packageName, action.Name}, "/")

				output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X,
					map[string]interface{}{
						wski18n.KEY_KEY:     parsers.YAML_KEY_ACTION,
						wski18n.KEY_NAME:    actionName,
						wski18n.KEY_PROJECT: aa[utils.OW_PROJECT_NAME]})
				wskprint.PrintOpenWhiskWarning(output)

				var err error
				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
					_, err := deployer.Client.Actions.Delete(actionName)
					return err
				})

				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// TODO() display "update" | "synced" messages pre/post
func (deployer *ServiceDeployer) RefreshManagedTriggers(ma map[string]interface{}) error {
	options := whisk.TriggerListOptions{}
	// Get list of triggers in your namespace
	triggers, _, err := deployer.Client.Triggers.List(&options)
	if err != nil {
		return err
	}
	// iterate over the list of triggers to determine whether any of them was part of managed project
	// and now deleted from manifest file we can determine that from the managed annotation
	// If a trigger has attached managed annotation with the project name equals to the current project name
	// but the project hash is different (project hash differs since the trigger is deleted from the manifest file)
	for _, trigger := range triggers {
		// trigger has attached managed annotation
		if a := trigger.Annotations.GetValue(utils.MANAGED); a != nil {
			// decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH
			ta := a.(map[string]interface{})
			if ta[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && ta[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] {
				// we have found a trigger which was earlier part of the current project
				output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X,
					map[string]interface{}{
						wski18n.KEY_KEY:     parsers.YAML_KEY_TRIGGER,
						wski18n.KEY_NAME:    trigger.Name,
						wski18n.KEY_PROJECT: ma[utils.OW_PROJECT_NAME]})
				wskprint.PrintOpenWhiskWarning(output)

				var err error
				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
					_, _, err := deployer.Client.Triggers.Delete(trigger.Name)
					return err
				})

				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// TODO() display "update" | "synced" messages pre/post
func (deployer *ServiceDeployer) RefreshManagedRules(ma map[string]interface{}) error {
	options := whisk.RuleListOptions{}
	// Get list of rules in your namespace
	rules, _, err := deployer.Client.Rules.List(&options)
	if err != nil {
		return err
	}
	// iterate over the list of rules to determine whether any of them was part of managed project
	// and now deleted from manifest file we can determine that from the managed annotation
	// If a rule has attached managed annotation with the project name equals to the current project name
	// but the project hash is different (project hash differs since the rule is deleted from the manifest file)
	for _, rule := range rules {
		// rule has attached managed annotation
		if a := rule.Annotations.GetValue(utils.MANAGED); a != nil {
			// decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH
			ta := a.(map[string]interface{})
			if ta[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && ta[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] {
				// we have found a trigger which was earlier part of the current project
				output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X,
					map[string]interface{}{
						wski18n.KEY_KEY:     parsers.YAML_KEY_RULE,
						wski18n.KEY_NAME:    rule.Name,
						wski18n.KEY_PROJECT: ma[utils.OW_PROJECT_NAME]})
				wskprint.PrintOpenWhiskWarning(output)

				var err error
				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
					_, err := deployer.Client.Rules.Delete(rule.Name)
					return err
				})

				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// TODO() display "update" | "synced" messages pre/post
func (deployer *ServiceDeployer) RefreshManagedPackages(ma map[string]interface{}) error {
	options := whisk.PackageListOptions{}
	// Get the list of packages in your namespace
	packages, _, err := deployer.Client.Packages.List(&options)
	if err != nil {
		return err
	}
	// iterate over each package to find managed annotations
	// check if "managed" annotation is attached to a package
	// when managed project name matches with the current project name and project
	// hash differs, indicates that the package was part of the current project but
	// now is deleted from the manifest file and should be undeployed.
	for _, pkg := range packages {
		if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil {
			// decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH
			pa := a.(map[string]interface{})
			// perform the similar check on the list of actions from this package
			// since package can not be deleted if its not empty (has any action or sequence)
			if err := deployer.RefreshManagedActions(pkg.Name, ma); err != nil {
				return err
			}
			// we have found a package which was earlier part of the current project
			if pa[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && pa[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] {
				output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X,
					map[string]interface{}{
						wski18n.KEY_KEY:     parsers.YAML_KEY_PACKAGE,
						wski18n.KEY_NAME:    pkg.Name,
						wski18n.KEY_PROJECT: pa[utils.OW_PROJECT_NAME]})
				wskprint.PrintOpenWhiskWarning(output)

				var err error
				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
					_, err := deployer.Client.Packages.Delete(pkg.Name)
					return err
				})

				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (deployer *ServiceDeployer) appendDepAnnotation(list whisk.KeyValueArr, pkg *whisk.Package) whisk.KeyValueArr {
	depExists := false
	if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil {
		//append annotations from this package to deps
		pkgName := parsers.PATH_SEPARATOR + pkg.Namespace + parsers.PATH_SEPARATOR + pkg.Name
		for _, dep := range list {
			if dep.Key == pkgName {
				depExists = true
			}
		}
		if !depExists {
			list = append(list, whisk.KeyValue{Key: pkgName, Value: a.(map[string]interface{})})
		}
	}
	return list
}

func (deployer *ServiceDeployer) RefreshManagedPackagesWithDependencies(ma map[string]interface{}) error {
	// iterate over each package from the given project
	for _, p := range deployer.Deployment.Packages {
		dependencyAnnotations := make(whisk.KeyValueArr, 0)
		// iterate over the list of dependencies of the package
		// dependencies could be labeled same as dependent package name
		// for example, "helloworld" where the package it depends on is also called "helloworld"
		// dependencies could be labeled different from the dependent package name
		// for example, "custom-helloworld" where the package it depends on it called "helloworld"
		for label, n := range p.Dependencies {
			// we do not append dependencies in whisk-managed in case of a package binding
			// for example when a package has dependencies on /whisk.system/ or any other /<namespace>/
			// the dependent packages are pre-installed and should not be managed by current project
			if !n.IsBinding {
				// find the package using dependency label
				pkg, _, err := deployer.Client.Packages.Get(label)
				if err != nil {
					return err
				}
				// if dependency label (custom-helloworld) is different from the dependent package name,
				// it must have binding set to the original package ("helloworld")
				if len(pkg.Binding.Name) != 0 {
					// having dependency on packages under /whisk.system is treated in a different way
					// in which dependent package under /whisk.system are not modified to add managed annotation
					// and parent package does not show this dependency in its managed annotation
					// because whisk.system packages comes pre-packaged and deployed with OpenWhisk server and not
					// deployed along with application deployments.
					// get the original package to retrieve its managed annotations
					pkg, _, err := deployer.Client.Packages.Get(pkg.Binding.Name)
					if err != nil {
						return err
					}
					dependencyAnnotations = deployer.appendDepAnnotation(dependencyAnnotations, pkg)
				} else {
					dependencyAnnotations = deployer.appendDepAnnotation(dependencyAnnotations, pkg)
				}
			}
		}
		updatedAnnotation, err := utils.AddDependentAnnotation(ma, dependencyAnnotations)
		if err != nil {
			return err
		}
		p.Package.Annotations.AddOrReplace(&updatedAnnotation)
	}
	if err := deployer.DeployPackages(); err != nil {
		return err
	}
	return nil
}

func (deployer *ServiceDeployer) DeployPackages() error {
	for _, pack := range deployer.Deployment.Packages {
		// "default" package is a reserved package name
		// all openwhisk entities will be deployed under
		// /<namespace> instead of /<namespace>/<package> and
		// therefore skip creating a new package and set
		// deployer.DeployActionInPackage to false which is set to true by default
		if strings.ToLower(pack.Package.Name) != parsers.DEFAULT_PACKAGE {
			err := deployer.createPackage(pack.Package)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Deploy Sequences into OpenWhisk
func (deployer *ServiceDeployer) DeploySequences() error {

	for _, pack := range deployer.Deployment.Packages {
		for _, action := range pack.Sequences {
			err := deployer.createAction(pack.Package.Name, action.Action)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Deploy Actions into OpenWhisk
func (deployer *ServiceDeployer) DeployActions() error {

	for _, pack := range deployer.Deployment.Packages {
		for _, action := range pack.Actions {
			err := deployer.createAction(pack.Package.Name, action.Action)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Deploy Triggers into OpenWhisk
func (deployer *ServiceDeployer) DeployTriggers() error {
	for _, trigger := range deployer.Deployment.Triggers {

		if feedname, isFeed := utils.IsFeedAction(trigger); isFeed {
			err := deployer.createFeedAction(trigger, feedname)
			if err != nil {
				return err
			}
		} else {
			err := deployer.createTrigger(trigger)
			if err != nil {
				return err
			}
		}

	}
	return nil

}

// Deploy Rules into OpenWhisk
func (deployer *ServiceDeployer) DeployRules() error {
	for _, rule := range deployer.Deployment.Rules {
		err := deployer.createRule(rule)
		if err != nil {
			return err
		}
	}
	return nil
}

// Deploy Apis into OpenWhisk
func (deployer *ServiceDeployer) DeployApis() error {
	var err error
	// NOTE: Only deploy either swagger or manifest defined api, but not both
	// NOTE: Swagger API takes precedence
	if deployer.Deployment.SwaggerApi != nil && deployer.Deployment.SwaggerApiOptions != nil {
		err = deployer.createSwaggerApi(deployer.Deployment.SwaggerApi)
		if err != nil {
			return err
		}
	} else {
		for _, api := range deployer.Deployment.Apis {
			err = deployer.createApi(api)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (deployer *ServiceDeployer) createBinding(packa *whisk.BindingPackage) error {

	displayPreprocessingInfo(wski18n.PACKAGE_BINDING, packa.Name, true)

	var err error
	var response *http.Response
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Packages.Insert(packa, true)
		return err
	})

	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, wski18n.PACKAGE_BINDING, true)
	}

	displayPostprocessingInfo(wski18n.PACKAGE_BINDING, packa.Name, true)
	return nil
}

func (deployer *ServiceDeployer) createPackage(packa *whisk.Package) error {

	displayPreprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, true)

	var err error
	var response *http.Response
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Packages.Insert(packa, true)
		return err
	})
	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_PACKAGE, true)
	}

	displayPostprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, true)
	return nil
}

func (deployer *ServiceDeployer) createTrigger(trigger *whisk.Trigger) error {

	displayPreprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, true)

	var err error
	var response *http.Response
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Triggers.Insert(trigger, true)
		return err
	})
	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_TRIGGER, true)
	}

	displayPostprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, true)
	return nil
}

func (deployer *ServiceDeployer) createFeedAction(trigger *whisk.Trigger, feedName string) error {

	displayPreprocessingInfo(wski18n.TRIGGER_FEED, trigger.Name, true)

	// to hold and modify trigger parameters, not passed by ref?
	params := make(map[string]interface{})

	// check for strings that are JSON
	for _, keyVal := range trigger.Parameters {
		params[keyVal.Key] = keyVal.Value
	}

	// TODO() define keys and lifecycle operation names as const
	params["authKey"] = deployer.ClientConfig.AuthToken
	params["lifecycleEvent"] = "CREATE"
	params["triggerName"] = "/" + deployer.Client.Namespace + "/" + trigger.Name

	pub := true
	t := &whisk.Trigger{
		Name:        trigger.Name,
		Annotations: trigger.Annotations,
		Publish:     &pub,
	}

	// triggers created using any of the feeds including cloudant, alarm, message hub etc
	// does not honor UPDATE or overwrite=true with CREATE
	// wskdeploy is designed such that, it updates trigger feeds if they exists
	// or creates new in case they are missing
	// To address trigger feed UPDATE issue, we are checking here if trigger feed
	// exists, if so, delete it and recreate it
	_, r, _ := deployer.Client.Triggers.Get(trigger.Name)
	if r.StatusCode == 200 {
		// trigger feed already exists so first lets delete it and then recreate it
		deployer.deleteFeedAction(trigger, feedName)
	}

	var err error
	var response *http.Response
	if err = deployer.createTrigger(t); err != nil {
		return err
	}
	qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace)
	if err != nil {
		return err
	}

	namespace := deployer.Client.Namespace
	deployer.Client.Namespace = qName.Namespace
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Actions.Invoke(qName.EntityName, params, true, false)
		return err
	})
	deployer.Client.Namespace = namespace

	if err != nil {
		// Remove the created trigger
		deployer.Client.Triggers.Delete(trigger.Name)

		retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
			_, _, err := deployer.Client.Triggers.Delete(trigger.Name)
			return err
		})

		return createWhiskClientError(err.(*whisk.WskError), response, wski18n.TRIGGER_FEED, false)
	}

	displayPostprocessingInfo(wski18n.TRIGGER_FEED, trigger.Name, true)
	return nil
}

func (deployer *ServiceDeployer) createRule(rule *whisk.Rule) error {
	displayPreprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, true)

	// The rule's trigger should include the namespace with pattern /namespace/trigger
	rule.Trigger = deployer.getQualifiedName(rule.Trigger.(string))
	// The rule's action should include the namespace and package with pattern
	// /namespace/package/action if that action was created under a package
	// otherwise action should include the namespace with pattern /namespace/action
	rule.Action = deployer.getQualifiedName(rule.Action.(string))

	var err error
	var response *http.Response
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Rules.Insert(rule, true)
		return err
	})

	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_RULE, true)
	}

	// Consecutive deployments of manifest containing trigger with feed action (and rule) result in inactive
	// rule. The rule seems to become inactive when its trigger get deleted (part of the wskdeploy feed action update)
	// Currently simply always setting rule status to active in case not specified implicitly
	_, _, err = deployer.Client.Rules.SetState(rule.Name, "active")
	if err != nil {
		return err
	}

	displayPostprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, true)
	return nil
}

// Utility function to call go-whisk framework to make action
func (deployer *ServiceDeployer) createAction(pkgname string, action *whisk.Action) error {
	// call ActionService through the Client
	if strings.ToLower(pkgname) != parsers.DEFAULT_PACKAGE {
		// the action will be created under package with pattern 'packagename/actionname'
		action.Name = strings.Join([]string{pkgname, action.Name}, "/")
	}

	displayPreprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, true)

	var err error
	var response *http.Response
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Actions.Insert(action, true)
		return err
	})

	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_ACTION, true)
	}

	displayPostprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, true)
	return nil
}

func (deployer *ServiceDeployer) getAnnotationsFromPackageActionOrSequence(packageActionName string) *whisk.KeyValueArr {

	if len(packageActionName) != 0 {
		// Split the package name and action name being searched for
		aActionName := strings.Split(packageActionName, parsers.PATH_SEPARATOR)

		// Attempt to locate the named action (or sequence) to return its annotations
		if pkg, found := deployer.Deployment.Packages[aActionName[0]]; found {
			if atemp, found := pkg.Actions[aActionName[1]]; found {
				return &(atemp.Action.Annotations)
			} else if atemp, found := pkg.Sequences[aActionName[1]]; found {
				return &(atemp.Action.Annotations)
			}
		}
	}
	return nil
}

// create api (API Gateway functionality)
func (deployer *ServiceDeployer) createApi(api *whisk.ApiCreateRequest) error {

	apiPath := api.ApiDoc.ApiName + " " + api.ApiDoc.GatewayBasePath +
		api.ApiDoc.GatewayRelPath + " " + api.ApiDoc.GatewayMethod
	displayPreprocessingInfo(parsers.YAML_KEY_API, apiPath, true)

	var err error
	var response *http.Response

	apiCreateReqOptions := deployer.Deployment.ApiOptions[apiPath]

	// Retrieve annotations on the action we are attempting to create an API for
	var actionAnnotations *whisk.KeyValueArr
	actionAnnotations = deployer.getAnnotationsFromPackageActionOrSequence(api.ApiDoc.Action.Name)

	// Process any special annotations (e.g., "require-whisk-auth") on the associated Action
	// NOTE: we do not throw an error if annotations are NOT found (nil) since this is already done in
	// the parsing phase and would be redundant.
	if actionAnnotations != nil {
		wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, fmt.Sprintf("Processing action annotations: %v", actionAnnotations))

		// If the "require-whisk-auth" annotation is present on the referenced action,
		// apply its user provided security key (i.e., the annotation's value) to the API
		if webaction.HasAnnotation(actionAnnotations, webaction.REQUIRE_WHISK_AUTH) {
			api.ApiDoc.Action.SecureKey = actionAnnotations.GetValue(webaction.REQUIRE_WHISK_AUTH)
		}
	}

	if len(deployer.Client.Config.ApigwTenantId) > 0 {
		// Use it to identify the IAM namespace
		apiCreateReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId
	} else {
		//  assume a CF namespace (SpaceGuid) which is part of the authtoken
		apiCreateReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0]
	}

	apiCreateReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken

	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Apis.Insert(api, apiCreateReqOptions, true)
		return err
	})

	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, true)
	}

	displayPostprocessingInfo(parsers.YAML_KEY_API, apiPath, true)
	return nil
}

// create api (API Gateway functionality) from swagger file
func (deployer *ServiceDeployer) createSwaggerApi(api *whisk.ApiCreateRequest) error {
	var err error
	var response *http.Response

	apiCreateReqOptions := deployer.Deployment.SwaggerApiOptions
	apiCreateReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken
	// In the case of IAM namespaces, we must use the ApigwTenantId as the SpaceGuid
	// IAM namespaces can be detected by seeing if the ApigwTenantId is populated
	if len(deployer.Client.Config.ApigwTenantId) > 0 {
		apiCreateReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId
	} else {
		apiCreateReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0]
	}

	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Apis.Insert(api, apiCreateReqOptions, true)
		return err
	})

	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, true)
	}

	return nil
}

func (deployer *ServiceDeployer) UnDeploy(verifiedPlan *DeploymentProject) error {
	if deployer.Preview == true {
		deployer.printDeploymentAssets(verifiedPlan)
		return nil
	}

	if err := deployer.unDeployAssets(verifiedPlan); err != nil {
		wskprint.PrintOpenWhiskError(wski18n.T(wski18n.T(wski18n.ID_MSG_UNDEPLOYMENT_FAILED)))
		return err
	}

	wskprint.PrintOpenWhiskSuccess(wski18n.T(wski18n.T(wski18n.ID_MSG_UNDEPLOYMENT_SUCCEEDED)))
	return nil
}

func (deployer *ServiceDeployer) UnDeployProject() error {
	if err := deployer.UnDeployProjectAssets(); err != nil {
		return err
	}
	return nil
}

func (deployer *ServiceDeployer) unDeployAssets(verifiedPlan *DeploymentProject) error {
	if err := deployer.UndeploySwaggerApis(verifiedPlan); err != nil {
		return err
	}
	if err := deployer.UnDeployApis(verifiedPlan); err != nil {
		return err
	}

	if err := deployer.UnDeployRules(verifiedPlan); err != nil {
		return err
	}

	if err := deployer.UnDeployTriggers(verifiedPlan); err != nil {
		return err
	}

	if err := deployer.UnDeploySequences(verifiedPlan); err != nil {
		return err
	}

	if err := deployer.UnDeployActions(verifiedPlan); err != nil {
		return err
	}

	if err := deployer.UnDeployPackages(verifiedPlan); err != nil {
		return err
	}

	if err := deployer.UnDeployDependencies(); err != nil {
		return err
	}

	return nil
}

func (deployer *ServiceDeployer) UnDeployDependencies() error {
	for _, pack := range deployer.Deployment.Packages {
		for depName, depRecord := range pack.Dependencies {
			output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_UNDEPLOYING_X_name_X,
				map[string]interface{}{wski18n.KEY_NAME: depName})
			whisk.Debug(whisk.DbgInfo, output)

			if depRecord.IsBinding {
				var err error
				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
					_, err := deployer.Client.Packages.Delete(depName)
					return err
				})
				if err != nil {
					return err
				}
			} else {

				depServiceDeployer, err := deployer.getDependentDeployer(depName, depRecord)
				if err != nil {
					return err
				}

				plan, err := depServiceDeployer.ConstructUnDeploymentPlan()
				if err != nil {
					return err
				}

				dependentPackages := []string{}
				for k := range depServiceDeployer.Deployment.Packages {
					dependentPackages = append(dependentPackages, k)
				}

				// delete binding pkg if the origin package name is different
				if ok := depServiceDeployer.Deployment.Packages[depName]; ok == nil {
					if _, _, ok := deployer.Client.Packages.Get(depName); ok == nil {
						var err error
						var response *http.Response
						err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
							response, err = deployer.Client.Packages.Delete(depName)
							return err
						})
						if err != nil {
							return createWhiskClientError(err.(*whisk.WskError), response, wski18n.PACKAGE_BINDING, false)
						}
					}
				}

				if err := depServiceDeployer.unDeployAssets(plan); err != nil {
					errString := wski18n.T(wski18n.ID_MSG_DEPENDENCY_UNDEPLOYMENT_FAILURE_X_name_X,
						map[string]interface{}{wski18n.KEY_NAME: depName})
					whisk.Debug(whisk.DbgError, errString)
					return err
				}
			}
			output = wski18n.T(wski18n.ID_MSG_DEPENDENCY_UNDEPLOYMENT_SUCCESS_X_name_X,
				map[string]interface{}{wski18n.KEY_NAME: depName})
			whisk.Debug(whisk.DbgInfo, output)
		}
	}

	return nil
}

func (deployer *ServiceDeployer) UnDeployPackages(deployment *DeploymentProject) error {
	for _, pack := range deployment.Packages {
		// "default" package is a reserved package name
		// all openwhisk entities were deployed under
		// /<namespace> instead of /<namespace>/<package> and
		// therefore skip deleting default package during undeployment
		if strings.ToLower(pack.Package.Name) != parsers.DEFAULT_PACKAGE {
			err := deployer.deletePackage(pack.Package)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (deployer *ServiceDeployer) UnDeploySequences(deployment *DeploymentProject) error {

	for _, pack := range deployment.Packages {
		for _, action := range pack.Sequences {
			err := deployer.deleteAction(pack.Package.Name, action.Action)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// DeployActions into OpenWhisk
func (deployer *ServiceDeployer) UnDeployActions(deployment *DeploymentProject) error {

	for _, pack := range deployment.Packages {
		for _, action := range pack.Actions {
			err := deployer.deleteAction(pack.Package.Name, action.Action)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Deploy Triggers into OpenWhisk
func (deployer *ServiceDeployer) UnDeployTriggers(deployment *DeploymentProject) error {

	for _, trigger := range deployment.Triggers {
		if feedname, isFeed := utils.IsFeedAction(trigger); isFeed {
			err := deployer.deleteFeedAction(trigger, feedname)
			if err != nil {
				return err
			}
		}
		err := deployer.deleteTrigger(trigger)
		if err != nil {
			return err
		}
	}

	return nil

}

// Deploy Rules into OpenWhisk
func (deployer *ServiceDeployer) UnDeployRules(deployment *DeploymentProject) error {

	for _, rule := range deployment.Rules {
		err := deployer.deleteRule(rule)
		if err != nil {
			return err
		}
	}
	return nil
}

func (deployer *ServiceDeployer) UnDeployApis(deployment *DeploymentProject) error {

	for _, api := range deployment.Apis {
		err := deployer.deleteApi(api)
		if err != nil {
			return err
		}
	}
	return nil
}

func (deployer *ServiceDeployer) UndeploySwaggerApis(deployment *DeploymentProject) error {
	api := deployment.SwaggerApi
	err := deployer.deleteSwaggerApi(api)
	if err != nil {
		return err
	}
	return nil
}

func (deployer *ServiceDeployer) deletePackage(packa *whisk.Package) error {

	displayPreprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, false)

	if _, _, ok := deployer.Client.Packages.Get(packa.Name); ok == nil {
		var err error
		var response *http.Response
		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
			response, err = deployer.Client.Packages.Delete(packa.Name)
			return err
		})

		if err != nil {
			return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_PACKAGE, false)
		}
	}
	displayPostprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, false)
	return nil
}

func (deployer *ServiceDeployer) deleteTrigger(trigger *whisk.Trigger) error {

	displayPreprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, false)

	if _, _, ok := deployer.Client.Triggers.Get(trigger.Name); ok == nil {
		var err error
		var response *http.Response
		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
			_, response, err = deployer.Client.Triggers.Delete(trigger.Name)
			return err
		})

		if err != nil {
			return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_TRIGGER, false)
		}
	}

	displayPostprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, false)
	return nil
}

func (deployer *ServiceDeployer) deleteFeedAction(trigger *whisk.Trigger, feedName string) error {

	displayPreprocessingInfo(parsers.YAML_KEY_FEED, trigger.Name, false)

	params := make(whisk.KeyValueArr, 0)
	// TODO() define keys and operations as const
	params = append(params, whisk.KeyValue{Key: "authKey", Value: deployer.ClientConfig.AuthToken})
	params = append(params, whisk.KeyValue{Key: "lifecycleEvent", Value: "DELETE"})
	params = append(params, whisk.KeyValue{Key: "triggerName", Value: "/" + deployer.Client.Namespace + "/" + trigger.Name})

	parameters := make(map[string]interface{})
	for _, keyVal := range params {
		parameters[keyVal.Key] = keyVal.Value
	}

	qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace)
	if err != nil {
		return err
	}

	if _, _, ok := deployer.Client.Triggers.Get(trigger.Name); ok != nil {
		displayPostprocessingInfo(parsers.YAML_KEY_FEED, trigger.Name, false)
		return nil
	}

	namespace := deployer.Client.Namespace
	deployer.Client.Namespace = qName.Namespace
	var response *http.Response
	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		_, response, err = deployer.Client.Actions.Invoke(qName.EntityName, parameters, true, false)
		return err
	})

	deployer.Client.Namespace = namespace

	if err != nil {
		wskErr := err.(*whisk.WskError)
		errString := wski18n.T(wski18n.ID_ERR_FEED_INVOKE_X_err_X_code_X,
			map[string]interface{}{wski18n.KEY_ERR: wskErr.Error(), wski18n.KEY_CODE: strconv.Itoa(wskErr.ExitCode)})
		whisk.Debug(whisk.DbgError, errString)
		return wskderrors.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode, response)

	}
	displayPostprocessingInfo(parsers.YAML_KEY_FEED, trigger.Name, false)
	return nil
}

func (deployer *ServiceDeployer) deleteRule(rule *whisk.Rule) error {

	displayPreprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, false)

	if _, _, ok := deployer.Client.Rules.Get(rule.Name); ok == nil {
		var err error
		var response *http.Response
		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
			response, err = deployer.Client.Rules.Delete(rule.Name)
			return err
		})

		if err != nil {
			return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_RULE, false)
		}
	}
	displayPostprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, false)
	return nil
}

func (deployer *ServiceDeployer) isApi(api *whisk.ApiCreateRequest) bool {
	apiReqOptions := new(whisk.ApiGetRequestOptions)
	apiReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken
	apiReqOptions.ApiBasePath = api.ApiDoc.GatewayBasePath
	if len(deployer.Client.Config.ApigwTenantId) > 0 {
		// Use it to identify the IAM namespace
		apiReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId
	} else {
		//  assume a CF namespaces (SpaceGuid) which is part of the authtoken
		apiReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0]
	}

	a := new(whisk.ApiGetRequest)

	retApi, _, err := deployer.Client.Apis.Get(a, apiReqOptions)
	if err == nil {
		if retApi.Apis != nil && len(retApi.Apis) > 0 &&
			retApi.Apis[0].ApiValue != nil {
			return true
		}

	}
	return false
}

// delete api (API Gateway functionality)
func (deployer *ServiceDeployer) deleteApi(api *whisk.ApiCreateRequest) error {

	apiPath := api.ApiDoc.ApiName + " " + api.ApiDoc.GatewayBasePath +
		api.ApiDoc.GatewayRelPath + " " + api.ApiDoc.GatewayMethod
	displayPreprocessingInfo(parsers.YAML_KEY_API, apiPath, false)

	if deployer.isApi(api) {
		var err error
		var response *http.Response

		apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
		apiDeleteReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken
		if len(deployer.Client.Config.ApigwTenantId) > 0 {
			// Use it to identify the IAM namespace
			apiDeleteReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId
		} else {
			//  assume a CF namespaces (SpaceGuid) which is part of the authtoken
			apiDeleteReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0]
		}
		apiDeleteReqOptions.ApiBasePath = api.ApiDoc.GatewayBasePath
		apiDeleteReqOptions.ApiRelPath = api.ApiDoc.GatewayRelPath
		apiDeleteReqOptions.ApiVerb = api.ApiDoc.GatewayMethod

		a := new(whisk.ApiDeleteRequest)

		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
			response, err = deployer.Client.Apis.Delete(a, apiDeleteReqOptions)
			return err
		})

		if err != nil {
			return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, false)
		}
	}
	displayPostprocessingInfo(parsers.YAML_KEY_API, apiPath, false)
	return nil
}

// delete api (API Gateway functionality) from swagger file
func (deployer *ServiceDeployer) deleteSwaggerApi(api *whisk.ApiCreateRequest) error {
	var err error
	var response *http.Response

	// If there is no swagger file do nothing
	if api == nil {
		return nil
	}
	swaggerString := api.ApiDoc.Swagger
	swaggerObj := new(whisk.ApiSwagger)
	err = json.Unmarshal([]byte(swaggerString), swaggerObj)
	if err != nil {
		return err
	}

	apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
	apiDeleteReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken
	apiDeleteReqOptions.ApiBasePath = swaggerObj.BasePath
	// In the case of IAM namespaces, we must use the ApigwTenantId as the SpaceGuid
	// IAM namespaces can be detected by seeing if the ApigwTenantId is populated
	if len(deployer.Client.Config.ApigwTenantId) > 0 {
		apiDeleteReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId
	} else {
		apiDeleteReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0]
	}

	a := new(whisk.ApiDeleteRequest)
	a.Swagger = swaggerString

	err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
		response, err = deployer.Client.Apis.Delete(a, apiDeleteReqOptions)
		return err
	})

	if err != nil {
		return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, true)
	}

	return nil
}

// Utility function to call go-whisk framework to delete action
func (deployer *ServiceDeployer) deleteAction(pkgname string, action *whisk.Action) error {
	// call ActionService through Client
	if pkgname != parsers.DEFAULT_PACKAGE {
		// the action will be deleted under package with pattern 'packagename/actionname'
		action.Name = strings.Join([]string{pkgname, action.Name}, "/")
	}

	displayPreprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, false)

	if _, _, ok := deployer.Client.Actions.Get(action.Name, false); ok == nil {
		var err error
		var response *http.Response
		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
			response, err = deployer.Client.Actions.Delete(action.Name)
			return err
		})

		if err != nil {
			return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_ACTION, false)

		}
	}
	displayPostprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, false)
	return nil
}

func retry(attempts int, sleep time.Duration, callback func() error) error {
	var err error
	for i := 0; ; i++ {
		err = callback()
		if i >= (attempts - 1) {
			break
		}
		if err != nil {
			wskErr := err.(*whisk.WskError)
			if wskErr.ExitCode == CONFLICT_CODE && strings.Contains(wskErr.Error(), CONFLICT_MESSAGE) {
				time.Sleep(sleep)
				warningMsg := wski18n.T(wski18n.ID_WARN_COMMAND_RETRY,
					map[string]interface{}{
						wski18n.KEY_CMD: strconv.Itoa(i + 1),
						wski18n.KEY_ERR: err.Error()})
				wskprint.PrintlnOpenWhiskWarning(warningMsg)
			} else {
				return err
			}
		} else {
			return err
		}
	}
	return err
}

//  getQualifiedName(name) returns a fully qualified name given a
//      (possibly fully qualified) resource name.
//
//  Examples:
//      (foo) => /ns/foo
//      (pkg/foo) => /ns/pkg/foo
//      (/ns/pkg/foo) => /ns/pkg/foo
func (deployer *ServiceDeployer) getQualifiedName(name string) string {
	namespace := deployer.ClientConfig.Namespace
	if strings.HasPrefix(name, "/") {
		return name
	} else if strings.HasPrefix(namespace, "/") {
		return fmt.Sprintf("%s/%s", namespace, name)
	}
	return fmt.Sprintf("/%s/%s", namespace, name)
}

func (deployer *ServiceDeployer) printDeploymentAssets(assets *DeploymentProject) {

	// TODO() review format
	wskprint.PrintlnOpenWhiskOutput(strings.Title(parsers.YAML_KEY_PACKAGES) + ":")
	for _, pack := range assets.Packages {
		wskprint.PrintlnOpenWhiskOutput(strings.Title(wski18n.KEY_NAME) + ": " + pack.Package.Name)
		wskprint.PrintlnOpenWhiskOutput("    " + wski18n.KEY_BINDINGS + ": ")
		for _, p := range pack.Package.Parameters {
			jsonValue, err := utils.PrettyJSON(p.Value)
			if err != nil {
				fmt.Printf("        - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE)
			} else {
				fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
			}
		}

		wskprint.PrintlnOpenWhiskOutput("    " + parsers.YAML_KEY_ANNOTATION + ": ")
		for _, p := range pack.Package.Annotations {
			fmt.Printf("        - %s : %v\n", p.Key, p.Value)

		}

		for key, dep := range pack.Dependencies {
			wskprint.PrintlnOpenWhiskOutput("  * " + wski18n.KEY_DEPENDENCY + ": " + key)
			wskprint.PrintlnOpenWhiskOutput("    " + wski18n.KEY_LOCATION + ": " + dep.Location)
			if !dep.IsBinding {
				wskprint.PrintlnOpenWhiskOutput("    " + wski18n.KEY_PATH + ": " + dep.ProjectPath)
			}
		}

		wskprint.PrintlnOpenWhiskOutput("")

		for _, action := range pack.Actions {
			wskprint.PrintlnOpenWhiskOutput("  * " + parsers.YAML_KEY_ACTION + ": " + action.Action.Name)
			wskprint.PrintlnOpenWhiskOutput("    " + wski18n.KEY_BINDINGS + ": ")
			for _, p := range action.Action.Parameters {

				if reflect.TypeOf(p.Value).Kind() == reflect.Map {
					if _, ok := p.Value.(map[interface{}]interface{}); ok {
						var temp map[string]interface{} = utils.ConvertInterfaceMap(p.Value.(map[interface{}]interface{}))
						fmt.Printf("        - %s : %v\n", p.Key, temp)
					} else {
						jsonValue, err := utils.PrettyJSON(p.Value)
						if err != nil {
							fmt.Printf("        - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE)
						} else {
							fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
						}
					}
				} else {
					jsonValue, err := utils.PrettyJSON(p.Value)
					if err != nil {
						fmt.Printf("        - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE)
					} else {
						fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
					}
				}

			}
			wskprint.PrintlnOpenWhiskOutput("    " + parsers.YAML_KEY_ANNOTATION + ": ")
			for _, p := range action.Action.Annotations {
				fmt.Printf("        - %s : %v\n", p.Key, p.Value)

			}
		}

		wskprint.PrintlnOpenWhiskOutput("")
		for _, action := range pack.Sequences {
			wskprint.PrintlnOpenWhiskOutput("  * " + parsers.YAML_KEY_SEQUENCE + ": " + action.Action.Name)
			wskprint.PrintlnOpenWhiskOutput("    " + parsers.YAML_KEY_ANNOTATION + ": ")
			for _, p := range action.Action.Annotations {
				fmt.Printf("        - %s : %v\n", p.Key, p.Value)

			}
		}

		wskprint.PrintlnOpenWhiskOutput("")
	}

	wskprint.PrintlnOpenWhiskOutput(wski18n.TRIGGERS + ":")
	for _, trigger := range assets.Triggers {
		wskprint.PrintlnOpenWhiskOutput("* " + parsers.YAML_KEY_TRIGGER + ": " + trigger.Name)
		wskprint.PrintlnOpenWhiskOutput("    " + wski18n.KEY_BINDINGS + ": ")

		for _, p := range trigger.Parameters {
			jsonValue, err := utils.PrettyJSON(p.Value)
			if err != nil {
				fmt.Printf("        - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE)
			} else {
				fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
			}
		}

		wskprint.PrintlnOpenWhiskOutput("    " + parsers.YAML_KEY_ANNOTATION + ": ")
		for _, p := range trigger.Annotations {
			fmt.Printf("        - %s : %v\n", p.Key, p.Value)

		}
	}

	wskprint.PrintlnOpenWhiskOutput("\n" + wski18n.RULES)
	for _, rule := range assets.Rules {
		wskprint.PrintlnOpenWhiskOutput("* " + parsers.YAML_KEY_RULE + ": " + rule.Name)
		wskprint.PrintlnOpenWhiskOutput("    " + parsers.YAML_KEY_ANNOTATION + ": ")
		for _, p := range rule.Annotations {
			fmt.Printf("        - %s : %v\n", p.Key, p.Value)

		}
		if reflect.TypeOf(rule.Trigger).Kind() == reflect.String {
			wskprint.PrintlnOpenWhiskOutput("    - " + parsers.YAML_KEY_TRIGGER + ": " + rule.Trigger.(string) + "\n    - " + parsers.YAML_KEY_ACTION + ": " + rule.Action.(string))
		} else if reflect.TypeOf(rule.Trigger).Kind() == reflect.Map {
			trigger := rule.Trigger.(map[string]interface{})
			triggerName := trigger["path"].(string) + parsers.PATH_SEPARATOR + trigger["name"].(string)
			action := rule.Action.(map[string]interface{})
			actionName := action["path"].(string) + parsers.PATH_SEPARATOR + action["name"].(string)
			wskprint.PrintlnOpenWhiskOutput("    - " + parsers.YAML_KEY_TRIGGER + ": " + triggerName + "\n    - " + parsers.YAML_KEY_ACTION + ": " + actionName)
		}

	}

	wskprint.PrintlnOpenWhiskOutput("")

}

func (deployer *ServiceDeployer) getDependentDeployer(depName string, depRecord dependencies.DependencyRecord) (*ServiceDeployer, error) {
	depServiceDeployer := NewServiceDeployer()
	projectPath := path.Join(depRecord.ProjectPath, depName+"-"+depRecord.Version)
	if len(depRecord.SubFolder) > 0 {
		projectPath = path.Join(projectPath, depRecord.SubFolder)
	}
	manifestPath := utils.GetManifestFilePath(projectPath)
	deploymentPath := utils.GetDeploymentFilePath(projectPath)
	depServiceDeployer.ProjectPath = projectPath
	depServiceDeployer.ManifestPath = manifestPath
	depServiceDeployer.DeploymentPath = deploymentPath
	depServiceDeployer.Preview = true

	depServiceDeployer.Client = deployer.Client
	depServiceDeployer.ClientConfig = deployer.ClientConfig

	// share the master dependency list
	depServiceDeployer.DependencyMaster = deployer.DependencyMaster

	return depServiceDeployer, nil
}

func displayPreprocessingInfo(entity string, name string, onDeploy bool) {

	var msgKey string
	if onDeploy {
		msgKey = wski18n.ID_MSG_ENTITY_DEPLOYING_X_key_X_name_X
	} else {
		msgKey = wski18n.ID_MSG_ENTITY_UNDEPLOYING_X_key_X_name_X
	}
	msg := wski18n.T(msgKey,
		map[string]interface{}{
			wski18n.KEY_KEY:  entity,
			wski18n.KEY_NAME: name})
	wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, msg)
}

func displayPostprocessingInfo(entity string, name string, onDeploy bool) {

	var msgKey string
	if onDeploy {
		msgKey = wski18n.ID_MSG_ENTITY_DEPLOYED_SUCCESS_X_key_X_name_X
	} else {
		msgKey = wski18n.ID_MSG_ENTITY_UNDEPLOYED_SUCCESS_X_key_X_name_X
	}
	msg := wski18n.T(msgKey,
		map[string]interface{}{
			wski18n.KEY_KEY:  entity,
			wski18n.KEY_NAME: name})
	wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, msg)
}

func createWhiskClientError(err *whisk.WskError, response *http.Response, entity string, onCreate bool) *wskderrors.WhiskClientError {

	var msgKey string
	if onCreate {
		msgKey = wski18n.ID_ERR_ENTITY_CREATE_X_key_X_err_X_code_X
	} else {
		msgKey = wski18n.ID_ERR_ENTITY_DELETE_X_key_X_err_X_code_X
	}
	errString := wski18n.T(msgKey,
		map[string]interface{}{
			wski18n.KEY_KEY:  entity,
			wski18n.KEY_ERR:  err.Error(),
			wski18n.KEY_CODE: strconv.Itoa(err.ExitCode)})
	wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, errString)

	// TODO() add errString as an AppendDetail() to WhiskClientError
	return wskderrors.NewWhiskClientError(err.Error(), err.ExitCode, response)
}

func (deployer *ServiceDeployer) reportInputs() error {
	// display project level inputs
	i := make(map[string]interface{}, 0)
	for name, param := range deployer.ProjectInputs {
		i[name] = param.Value
	}
	projectInputs := parsers.DisplayInputs{Name: deployer.ProjectName, Inputs: i}
	j, err := json.MarshalIndent(projectInputs, "", " ")
	if err != nil {
		return err
	}
	wskprint.PrintlnOpenWhiskOutput(string(j))

	// display package level inputs
	// iterate over each package and print inputs section of each package
	for _, pkg := range deployer.Deployment.Packages {
		i := make(map[string]interface{}, 0)
		for name, param := range pkg.Inputs.Inputs {
			if _, ok := deployer.ProjectInputs[name]; !ok {
				i[name] = param.Value
			}
		}
		packageInputs := parsers.DisplayInputs{Name: pkg.Package.Name, Inputs: i}
		j, err := json.MarshalIndent(packageInputs, "", "  ")
		if err != nil {
			return err
		}
		wskprint.PrintlnOpenWhiskOutput(string(j))

		for _, d := range pkg.Dependencies {
			i := make(map[string]interface{}, 0)
			for _, param := range d.Parameters {
				i[param.Key] = param.Value
			}
			depInputs := parsers.DisplayInputs{Name: d.Location, Inputs: i}
			j, err := json.MarshalIndent(depInputs, "", " ")
			if err != nil {
				return err
			}
			wskprint.PrintlnOpenWhiskOutput(string(j))
		}

		for _, a := range pkg.Actions {
			i := make(map[string]interface{}, 0)
			for _, param := range a.Action.Parameters {
				i[param.Key] = param.Value
			}

			actionInputs := parsers.DisplayInputs{Name: a.Action.Name, Inputs: i}
			j, err := json.MarshalIndent(actionInputs, "", " ")
			if err != nil {
				return err
			}
			wskprint.PrintlnOpenWhiskOutput(string(j))
		}

		for _, s := range pkg.Sequences {
			i := make(map[string]interface{}, 0)
			for _, param := range s.Action.Parameters {
				i[param.Key] = param.Value
			}
			seqInputs := parsers.DisplayInputs{Name: s.Action.Name, Inputs: i}
			j, err := json.MarshalIndent(seqInputs, "", " ")
			if err != nil {
				return err
			}
			wskprint.PrintlnOpenWhiskOutput(string(j))
		}
	}

	for _, trigger := range deployer.Deployment.Triggers {
		i := make(map[string]interface{}, 0)
		for _, param := range trigger.Parameters {
			i[param.Key] = param.Value
		}
		triggerInputs := parsers.DisplayInputs{Name: trigger.Name, Inputs: i}
		j, err := json.MarshalIndent(triggerInputs, "", " ")
		if err != nil {
			return err
		}
		wskprint.PrintlnOpenWhiskOutput(string(j))
	}
	return nil
}
