package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
)

func main() {

	directories, err := GetAllSubDirectories("./../charts")
	if err != nil {
		log.Fatalf("unable to get chart categories details, err: %v", err)
	}
	for _, directory := range directories {

		CRNames := []string{}

		// set path of combined CR file path
		filePath := directory + "/experiments.yaml"

		if isFileExist := IsFileExist(filePath); !isFileExist {
			if err := TouchFile(filePath); err != nil {
				log.Fatalf("unable to create the %v file, err: %v", filePath, err)
			}
		}

		// initialize the empty file
		if err := InitializeFile(filePath); err != nil {
			log.Fatalf("unable to initialize %v file, err: %v", filePath, err)
		}

		// get the list of all sub-directories
		subDirectories, err := GetAllSubDirectories(directory)
		if err != nil {
			log.Fatalf("unable to get experiment details, err: %v", err)
		}

		for _, subdirectory := range subDirectories {
			isFileExist := IsFileExist(subdirectory + "/experiment.yaml")
			if isFileExist {

				CRName, err := GetCRName(subdirectory + "/experiment.yaml")
				if err != nil || CRName == "" {
					log.Fatalf("unable to extract the CR name for %v file, err: %v", subdirectory+"/experiment.yaml", err)
				}

				if !Contains(CRName, CRNames) {
					if err := CopyData(filePath, subdirectory+"/experiment.yaml"); err != nil {
						log.Fatalf("unable to copy data for %v experiment, err: %v", subdirectory, err)
					}
					CRNames = append(CRNames, CRName)
				}
			}
		}

		log.Printf("validating combined charts for: %v", directory)
		command := exec.Command("cat", directory+"/experiments.yaml")
		data, _ := ExecuteCommand(command)
		log.Printf("data: %v\n", data)

	}

}

// IsFileExist check for the existance of given file/path
func IsFileExist(path string) bool {
	_, err := os.Stat(path)

	if os.IsNotExist(err) {
		return false
	}
	return true
}

// TouchFile create an empty file
func TouchFile(name string) error {
	file, err := os.OpenFile(name, os.O_RDONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	return file.Close()
}

// CopyData copy the file data from source file to destination file
func CopyData(destination, source string) error {

	in, err := os.Open(source)
	if err != nil {
		return err
	}
	out, err := os.OpenFile(destination, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return err
	}

	io.Copy(out, in)
	out.WriteString("\n---\n")
	return nil
}

// GetAllSubDirectories return the list of all available sub-directories
func GetAllSubDirectories(parent string) ([]string, error) {
	command := exec.Command("find", parent, "-maxdepth", "1", "-mindepth", "1", "-type", "d")
	out, err := ExecuteCommand(command)
	if err != nil {
		return nil, err
	}
	directories := strings.Split(out, "\n")
	return directories, err
}

// ExecuteCommand execute the shell commands
func ExecuteCommand(command *exec.Cmd) (string, error) {
	var out, stderr bytes.Buffer
	command.Stdout = &out
	command.Stderr = &stderr
	if err := command.Run(); err != nil {
		log.Printf("Error String: %v", stderr.String())
		return "", fmt.Errorf("Unable to execute command, err: %v", stderr.String())
	}
	return strings.TrimSpace(out.String()), nil
}

// InitializeFile create an empty file and initialise it with concat string
func InitializeFile(filePath string) error {
	initializeFile, err := os.OpenFile(filePath, os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	initializeFile.WriteString("\n---\n")
	return nil
}

// GetCRName return the name of cr from the manifest
func GetCRName(from string) (string, error) {
	decUnstructured := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
	// Decode YAML manifest into unstructured.Unstructured
	child := &unstructured.Unstructured{}
	childData, err := ioutil.ReadFile(from)
	if err != nil {
		return "", err
	}

	_, _, err = decUnstructured.Decode(childData, nil, child)
	if err != nil {
		return "", err
	}

	return child.GetName(), nil
}

// Contains check for the existance of elemennt inside slice
func Contains(element string, slice []string) bool {

	for index := range slice {
		if slice[index] == element {
			return true
		}
	}
	return false
}
