/*
Copyright 2024 The Flux authors

Licensed 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.
*/

// Forked from: https://github.com/drone/envsubst
// MIT License
// Copyright (c) 2017 drone.io.

package envsubst

import (
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/fluxcd/pkg/envsubst/path"
)

// defines a parameter substitution function.
type substituteFunc func(string, ...string) string

// toLen returns the length of string s.
func toLen(s string, args ...string) string {
	return strconv.Itoa(len(s))
}

// toLower returns a copy of the string s with all characters
// mapped to their lower case.
func toLower(s string, args ...string) string {
	return strings.ToLower(s)
}

// toUpper returns a copy of the string s with all characters
// mapped to their upper case.
func toUpper(s string, args ...string) string {
	return strings.ToUpper(s)
}

// toLowerFirst returns a copy of the string s with the first
// character mapped to its lower case.
func toLowerFirst(s string, args ...string) string {
	if s == "" {
		return s
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}

// toUpperFirst returns a copy of the string s with the first
// character mapped to its upper case.
func toUpperFirst(s string, args ...string) string {
	if s == "" {
		return s
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToUpper(r)) + s[n:]
}

// toDefault returns a copy of the string s if not empty, else
// returns a concatenation of the args without a separator.
func toDefault(s string, args ...string) string {
	if len(s) == 0 && len(args) > 0 {
		// don't use any separator
		s = strings.Join(args, "")
	}
	return s
}

// toSubstr returns a slice of the string s at the specified
// length and position.
func toSubstr(s string, args ...string) string {
	if len(args) == 0 {
		return s // should never happen
	}

	pos, err := strconv.Atoi(args[0])
	if err != nil {
		// bash returns the string if the position
		// cannot be parsed.
		return s
	}

	if pos < 0 {
		// if pos is negative (counts from the end) add it
		// to length to get first character offset
		pos = len(s) + pos

		// if negative offset exceeds the length of the string
		// start from 0
		if pos < 0 {
			pos = 0
		}
	}

	if len(args) == 1 {
		if pos < len(s) {
			return s[pos:]
		}
		// if the position exceeds the length of the
		// string an empty string is returned
		return ""
	}

	length, err := strconv.Atoi(args[1])
	if err != nil {
		// bash returns the string if the length
		// cannot be parsed.
		return s
	}

	if pos+length >= len(s) {
		if pos < len(s) {
			// if the position exceeds the length of the
			// string just return the rest of it like bash
			return s[pos:]
		}
		// if the position exceeds the length of the
		// string an empty string is returned
		return ""
	}

	return s[pos : pos+length]
}

// replaceAll returns a copy of the string s with all instances
// of the substring replaced with the replacement string.
func replaceAll(s string, args ...string) string {
	switch len(args) {
	case 0:
		return s
	case 1:
		return strings.Replace(s, args[0], "", -1)
	default:
		return strings.Replace(s, args[0], args[1], -1)
	}
}

// replaceFirst returns a copy of the string s with the first
// instance of the substring replaced with the replacement string.
func replaceFirst(s string, args ...string) string {
	switch len(args) {
	case 0:
		return s
	case 1:
		return strings.Replace(s, args[0], "", 1)
	default:
		return strings.Replace(s, args[0], args[1], 1)
	}
}

// replacePrefix returns a copy of the string s with the matching
// prefix replaced with the replacement string.
func replacePrefix(s string, args ...string) string {
	if len(args) != 2 {
		return s
	}
	if strings.HasPrefix(s, args[0]) {
		return strings.Replace(s, args[0], args[1], 1)
	}
	return s
}

// replaceSuffix returns a copy of the string s with the matching
// suffix replaced with the replacement string.
func replaceSuffix(s string, args ...string) string {
	if len(args) != 2 {
		return s
	}
	if strings.HasSuffix(s, args[0]) {
		s = strings.TrimSuffix(s, args[0])
		s = s + args[1]
	}
	return s
}

// TODO

func trimShortestPrefix(s string, args ...string) string {
	if len(args) != 0 {
		s = trimShortest(s, args[0])
	}
	return s
}

func trimShortestSuffix(s string, args ...string) string {
	if len(args) != 0 {
		r := reverse(s)
		rarg := reverse(args[0])
		s = reverse(trimShortest(r, rarg))
	}
	return s
}

func trimLongestPrefix(s string, args ...string) string {
	if len(args) != 0 {
		s = trimLongest(s, args[0])
	}
	return s
}

func trimLongestSuffix(s string, args ...string) string {
	if len(args) != 0 {
		r := reverse(s)
		rarg := reverse(args[0])
		s = reverse(trimLongest(r, rarg))
	}
	return s
}

func trimShortest(s, arg string) string {
	var shortestMatch string
	for i := 0; i < len(s); i++ {
		match, err := path.Match(arg, s[0:len(s)-i])

		if err != nil {
			return s
		}

		if match {
			shortestMatch = s[0 : len(s)-i]
		}
	}

	if shortestMatch != "" {
		return strings.TrimPrefix(s, shortestMatch)
	}

	return s
}

func trimLongest(s, arg string) string {
	for i := 0; i < len(s); i++ {
		match, err := path.Match(arg, s[0:len(s)-i])

		if err != nil {
			return s
		}

		if match {
			return strings.TrimPrefix(s, s[0:len(s)-i])
		}
	}

	return s
}

func reverse(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}
