// Referenced from https://github.com/yuriisk/clang-tidy-converter/blob/master/clang_tidy_converter/parser/clang_tidy_parser.py
package clang_tidy

import (
	"fmt"
	"strings"
)

type json_message struct {
	Type        string                 `json:"type"`
	Check_name  string                 `json:"check_name"`
	Description string                 `json:"description"`
	Content     map[string]interface{} `json:"content"`
	Categories  []string               `json:"categories"`
	Location    map[string]interface{} `json:"location"`
	Trace       map[string]interface{} `json:"trace"`
	Severity    string                 `json:"severity"`
	Fingerprint string                 `json:"fingerprint"`
}

func format(messages []ClangMessage) []json_message {
	formatted_messages := make([]json_message, len(messages))
	for i, message := range messages {
		formatted_messages[i] = format_message(message)
	}
	return formatted_messages
}

func format_message(message ClangMessage) json_message {
	result := json_message{
		Type:        "issue",
		Check_name:  message.diagnosticName,
		Description: message.message,
		Content:     extract_content(message),
		Categories:  extract_categories(message),
		Location:    extract_location(message),
		Trace:       extract_trace(message),
		Severity:    extract_severity(message),
		Fingerprint: "",
		// Fingerprint: generate_fingerprint(message),
	}
	return result
}

func messages_to_text(messages []ClangMessage) []string {
	text_lines := []string{}
	for _, message := range messages {
		text_lines = append(text_lines, fmt.Sprintf("%s:%d:%d: %s", message.filepath, message.line, message.column, message.message))
		text_lines = append(text_lines, message.detailsLines...)
		text_lines = append(text_lines, messages_to_text(message.children)...)
	}
	return text_lines
}

func extract_content(message ClangMessage) map[string]interface{} {
	detailLines := ""
	for _, line := range message.detailsLines {
		if line == "" {
			continue
		}
		detailLines += (line + "\n")
	}
	for _, line := range messages_to_text(message.children) {
		if line == "" {
			continue
		}
		detailLines += (line + "\n")
	}
	result := map[string]interface{}{
		"body": "```\n" + detailLines + "```",
	}
	return result
}

func remove_duplicates(list []string) []string {
	uniqueMap := make(map[string]bool)
	for _, v := range list {
		uniqueMap[v] = true
	}
	result := []string{}
	for k := range uniqueMap {
		result = append(result, k)
	}
	return result
}

func extract_categories(message ClangMessage) []string {
	BUGRISC_CATEGORY := "Bug Risk"
	CLARITY_CATEGORY := "Clarity"
	COMPATIBILITY_CATEGORY := "Compatibility"
	COMPLEXITY_CATEGORY := "Complexity"
	DUPLICATION_CATEGORY := "Duplication"
	PERFORMANCE_CATEGORY := "Performance"
	SECURITY_CATEGORY := "Security"
	STYLE_CATEGORY := "Style"

	categories := []string{}
	if strings.Contains(message.diagnosticName, "bugprone") {
		categories = append(categories, BUGRISC_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "modernize") {
		categories = append(categories, COMPATIBILITY_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "portability") {
		categories = append(categories, COMPATIBILITY_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "performance") {
		categories = append(categories, PERFORMANCE_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "readability") {
		categories = append(categories, CLARITY_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "cloexec") {
		categories = append(categories, SECURITY_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "security") {
		categories = append(categories, SECURITY_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "naming") {
		categories = append(categories, STYLE_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "misc") {
		categories = append(categories, STYLE_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "cppcoreguidelines") {
		categories = append(categories, STYLE_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "hicpp") {
		categories = append(categories, STYLE_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "simplify") {
		categories = append(categories, COMPLEXITY_CATEGORY)
	}
	if strings.Contains(message.diagnosticName, "redundant") {
		categories = append(categories, DUPLICATION_CATEGORY)
	}
	if strings.HasPrefix(message.diagnosticName, "boost-use-to-string") {
		categories = append(categories, COMPATIBILITY_CATEGORY)
	}
	if len(categories) == 0 {
		categories = append(categories, BUGRISC_CATEGORY)
	}
	return remove_duplicates(categories)
}

func extract_location(message ClangMessage) map[string]interface{} {
	location := map[string]interface{}{
		"path": message.filepath,
		"lines": map[string]interface{}{
			"begin": message.line,
		},
	}
	return location
}

func extract_other_locations(message ClangMessage) []map[string]interface{} {
	location_list := []map[string]interface{}{}
	for _, child := range message.children {
		location_list = append(location_list, extract_location(child))
		location_list = append(location_list, extract_other_locations(child)...)
	}
	return location_list
}

func extract_trace(message ClangMessage) map[string]interface{} {
	result := map[string]interface{}{
		"locations": extract_other_locations(message),
	}
	return result
}

func extract_severity(message ClangMessage) string {
	switch message.level {
	case NOTE:
		return "info"
	case REMARK:
		return "minor"
	case WARNING:
		return "major"
	case ERROR:
		return "critical"
	case FATAL:
		return "blocker"
	default:
		return "unknown"
	}
}

// func generate_fingerprint(message ClangMessage) string {
// 	h := md5.New()
// 	h.Write([]byte(message.filepath))
// 	h.Write([]byte(fmt.Sprintf("%d", message.line)))
// 	h.Write([]byte(fmt.Sprintf("%d", message.column)))
// 	h.Write([]byte(message.message))
// 	h.Write([]byte(message.diagnosticName))
// 	for _, child := range message.children {
// 		childFingerprint := generate_fingerprint(child)
// 		h.Write([]byte(childFingerprint))
// 	}
// 	return hex.EncodeToString(h.Sum(nil))
// }