// Copyright 2019 Istio 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.

package istioio

import (
	"strings"
	"text/scanner"
	"unicode"
)

// verifier for output of a shell command.
type verifier func(ctx Context, name, expectedOutput, actualOutput string)

// verifiers supported by in the command scripts.
var verifiers = map[string]verifier{
	"":            verifyTokens, // Default
	"token":       verifyTokens,
	"contains":    verifyContains,
	"notContains": verifyNotContains,
}

// verifyTokens tokenizes the output and compares against the tokens from the given file.
func verifyTokens(ctx Context, name, expectedOutput, actualOutput string) {
	// Tokenize the content and the file.
	expectedTokenLines := tokenize(expectedOutput)
	actualTokenLines := tokenize(actualOutput)

	if len(expectedTokenLines) != len(actualTokenLines) {
		ctx.Fatalf("verification failed for command %s: line count: (expected %d, found %d). Expected:\n%s\nto match:\n%s",
			name, len(expectedTokenLines), len(actualTokenLines), actualOutput, expectedOutput)
	}

	for lineIndex := 0; lineIndex < len(expectedTokenLines); lineIndex++ {
		expectedTokens := expectedTokenLines[lineIndex]
		actualTokens := actualTokenLines[lineIndex]

		if len(expectedTokens) != len(actualTokens) {
			ctx.Fatalf("verification failed for command %s [line %d]: token count (expected %d, found %d). Expected:\n%s\nto match:\n%s",
				name, lineIndex, len(expectedTokens), len(actualTokens), actualOutput, expectedOutput)
		}

		for tokenIndex := 0; tokenIndex < len(expectedTokens); tokenIndex++ {
			expectedToken := expectedTokens[tokenIndex]
			if expectedToken == "?" {
				// The value was a wildcard, matches anything.
				continue
			}

			actualToken := actualTokens[tokenIndex]
			if expectedToken != actualToken {
				ctx.Fatalf("verification failed for command %s [line %d]: token %d (expected %s, found %s). Expected:\n%s\nto match:\n%s",
					name, lineIndex, tokenIndex, expectedToken, actualToken, actualOutput, expectedOutput)
			}
		}
	}
}

func tokenize(content string) [][]string {
	inputLines := strings.Split(strings.TrimSpace(content), "\n")
	tokenLines := make([][]string, 0)

	for _, inputLine := range inputLines {
		tokens := make([]string, 0)

		var s scanner.Scanner

		// Configure the token characters.
		s.IsIdentRune = func(ch rune, i int) bool {
			return ch == '_' || ch == '-' || ch == '.' || unicode.IsLetter(ch) || unicode.IsDigit(ch)
		}

		s.Init(strings.NewReader(inputLine))
		for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
			tokens = append(tokens, s.TokenText())
		}
		tokenLines = append(tokenLines, tokens)
	}
	return tokenLines
}

func verifyContains(ctx Context, name, expectedOutput, actualOutput string) {
	if !strings.Contains(actualOutput, expectedOutput) {
		ctx.Fatalf("verification failed for command %s: output does not contain expected text.\nExpected:\n%s\nOutput:\n%s",
			name, expectedOutput, actualOutput)
		return
	}
}

func verifyNotContains(ctx Context, name, expectedOutput, actualOutput string) {
	if strings.Contains(actualOutput, expectedOutput) {
		ctx.Fatalf("verification failed for command %s: output contains not expected text.\nNot Expected:\n%s\nOutput:\n%s",
			name, expectedOutput, actualOutput)
		return
	}
}
