/*
 * 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 commands

import (
	"errors"
	"fmt"
	"github.com/apache/openwhisk-cli/wski18n"
	"github.com/apache/openwhisk-client-go/whisk"
	"strings"
)

type QualifiedName struct {
	namespace   string // namespace. does not include leading '/'.  may be "" (i.e. default namespace)
	packageName string // package.  may be "".  does not include leading/trailing '/'
	entity      string // entity.  should not be ""
	EntityName  string // pkg+entity
}

///////////////////////////
// QualifiedName Methods //
///////////////////////////

//  GetFullQualifiedName() returns a full qualified name in proper string format
//      from qualifiedName with proper syntax.
//  Example: /namespace/[package/]entity
func (qualifiedName *QualifiedName) GetFullQualifiedName() string {
	output := []string{}

	if len(qualifiedName.GetNamespace()) > 0 {
		output = append(output, "/", qualifiedName.GetNamespace(), "/")
	}
	if len(qualifiedName.GetPackageName()) > 0 {
		output = append(output, qualifiedName.GetPackageName(), "/")
	}
	output = append(output, qualifiedName.GetEntity())

	return strings.Join(output, "")
}

//  GetPackageName() returns the package name from qualifiedName without a
//      leading '/'
func (qualifiedName *QualifiedName) GetPackageName() string {
	return qualifiedName.packageName
}

//  GetEntityName() returns the entity name ([package/]entity) of qualifiedName
//      without a leading '/'
func (qualifiedName *QualifiedName) GetEntityName() string {
	return qualifiedName.EntityName
}

//  GetEntity() returns the name of entity in qualifiedName without a leading '/'
func (qualifiedName *QualifiedName) GetEntity() string {
	return qualifiedName.entity
}

//  GetNamespace() returns the name of the namespace in qualifiedName without
//      a leading '/'
func (qualifiedName *QualifiedName) GetNamespace() string {
	return qualifiedName.namespace
}

//  NewQualifiedName(name) initializes and constructs a (possibly fully qualified)
//      QualifiedName struct.
//
//      NOTE: If the given qualified name is None, then this is a default qualified
//          name and it is resolved from properties.
//      NOTE: If the namespace is missing from the qualified name, the namespace
//          is also resolved from the property file.
//
//  Examples:
//      foo => qualifiedName {namespace: "_", entityName: foo}
//      pkg/foo => qualifiedName {namespace: "_", entityName: pkg/foo}
//      /ns/foo => qualifiedName {namespace: ns, entityName: foo}
//      /ns/pkg/foo => qualifiedName {namespace: ns, entityName: pkg/foo}
func NewQualifiedName(name string) (*QualifiedName, error) {
	qualifiedName := new(QualifiedName)

	// If name has a preceding delimiter (/), or if it has two delimiters with a
	// leading non-empty string, then it contains a namespace. Otherwise the name
	// does not specify a namespace, so default the namespace to the namespace
	// value set in the properties file; if that is not set, use "_"
	name = addLeadSlash(name)
	parts := strings.Split(name, "/")
	if strings.HasPrefix(name, "/") {
		qualifiedName.namespace = parts[1]

		if len(parts) < 2 || len(parts) > 4 {
			return qualifiedName, qualifiedNameNotSpecifiedErr()
		}

		for i := 1; i < len(parts); i++ {
			if len(parts[i]) == 0 || parts[i] == "." {
				return qualifiedName, qualifiedNameNotSpecifiedErr()
			}
		}

		qualifiedName.EntityName = strings.Join(parts[2:], "/")
		if len(parts) == 4 {
			qualifiedName.packageName = parts[2]
		}
		qualifiedName.entity = parts[len(parts)-1]
	} else {
		if len(name) == 0 || name == "." {
			return qualifiedName, qualifiedNameNotSpecifiedErr()
		}

		qualifiedName.entity = parts[len(parts)-1]
		if len(parts) == 2 {
			qualifiedName.packageName = parts[0]
		}
		qualifiedName.EntityName = name
		qualifiedName.namespace = getNamespaceFromProp()
	}

	whisk.Debug(whisk.DbgInfo, "Qualified pkg+entity (EntityName): %s\n", qualifiedName.GetEntityName())
	whisk.Debug(whisk.DbgInfo, "Qualified namespace: %s\n", qualifiedName.GetNamespace())
	whisk.Debug(whisk.DbgInfo, "Qualified package: %s\n", qualifiedName.GetPackageName())
	whisk.Debug(whisk.DbgInfo, "Qualified entity: %s\n", qualifiedName.GetEntity())

	return qualifiedName, nil
}

/////////////////////
// Error Functions //
/////////////////////

//  qualifiedNameNotSpecifiedErr() returns generic whisk error for
//      invalid qualified names detected while building a new
//      QualifiedName struct.
func qualifiedNameNotSpecifiedErr() error {
	whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
	errStr := wski18n.T("A valid qualified name must be specified.")
	return whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
}

//  NewQualifiedNameError(entityName, err) returns specific whisk error
//      for invalid qualified names.
func NewQualifiedNameError(entityName string, err error) error {
	whisk.Debug(whisk.DbgError, "NewQualifiedName(%s) failed: %s\n", entityName, err)

	errMsg := wski18n.T(
		"'{{.name}}' is not a valid qualified name: {{.err}}",
		map[string]interface{}{
			"name": entityName,
			"err":  err,
		})

	return whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
}

///////////////////////////
// Helper/Misc Functions //
///////////////////////////

//  addLeadSlash(name) returns a (possibly fully qualified) resource name,
//      inserting a leading '/' if it is of 3 parts (namespace/package/action)
//      and lacking the leading '/'.
func addLeadSlash(name string) string {
	parts := strings.Split(name, "/")
	if len(parts) == 3 && parts[0] != "" {
		name = "/" + name
	}
	return name
}

//  getNamespaceFromProp() returns a namespace from Properties if one exists,
//      else defaults to returning "_"
func getNamespaceFromProp() string {
	namespace := "_"

	if Properties.Namespace != "" {
		namespace = Properties.Namespace
	}

	return namespace
}

//  getQualifiedName(name, namespace) returns a fully qualified name given a
//      (possibly fully qualified) resource name and optional namespace.
//
//  Examples:
//      (foo, None) => /_/foo
//      (pkg/foo, None) => /_/pkg/foo
//      (foo, ns) => /ns/foo
//      (/ns/pkg/foo, None) => /ns/pkg/foo
//      (/ns/pkg/foo, otherns) => /ns/pkg/foo
func getQualifiedName(name string, namespace string) string {
	name = addLeadSlash(name)
	if strings.HasPrefix(name, "/") {
		return name
	} else if strings.HasPrefix(namespace, "/") {
		return fmt.Sprintf("%s/%s", namespace, name)
	} else {
		if len(namespace) == 0 {
			namespace = Properties.Namespace
		}
		return fmt.Sprintf("/%s/%s", namespace, name)
	}
}
