// Copyright (c) 2021 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package binary

import (
	"encoding/hex"
	"fmt"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"
)

// Options to control how Node.XMLString behaves.
var (
	IndentXML            = false
	MaxBytesToPrintAsHex = 128
)

// XMLString converts the Node to its XML representation
func (n *Node) XMLString() string {
	content := n.contentString()
	if len(content) == 0 {
		return fmt.Sprintf("<%[1]s%[2]s/>", n.Tag, n.attributeString())
	}
	newline := "\n"
	if len(content) == 1 || !IndentXML {
		newline = ""
	}
	return fmt.Sprintf("<%[1]s%[2]s>%[4]s%[3]s%[4]s</%[1]s>", n.Tag, n.attributeString(), strings.Join(content, newline), newline)
}

func (n *Node) attributeString() string {
	if len(n.Attrs) == 0 {
		return ""
	}
	stringAttrs := make([]string, len(n.Attrs)+1)
	i := 1
	for key, value := range n.Attrs {
		stringAttrs[i] = fmt.Sprintf(`%s="%v"`, key, value)
		i++
	}
	sort.Strings(stringAttrs)
	return strings.Join(stringAttrs, " ")
}

func printable(data []byte) string {
	if !utf8.Valid(data) {
		return ""
	}
	str := string(data)
	for _, c := range str {
		if !unicode.IsPrint(c) {
			return ""
		}
	}
	return str
}

func (n *Node) contentString() []string {
	split := make([]string, 0)
	switch content := n.Content.(type) {
	case []Node:
		for _, item := range content {
			split = append(split, strings.Split(item.XMLString(), "\n")...)
		}
	case []byte:
		if strContent := printable(content); len(strContent) > 0 {
			if IndentXML {
				split = append(split, strings.Split(string(content), "\n")...)
			} else {
				split = append(split, strings.ReplaceAll(string(content), "\n", "\\n"))
			}
		} else if len(content) > MaxBytesToPrintAsHex {
			split = append(split, fmt.Sprintf("<!-- %d bytes -->", len(content)))
		} else if !IndentXML {
			split = append(split, hex.EncodeToString(content))
		} else {
			hexData := hex.EncodeToString(content)
			for i := 0; i < len(hexData); i += 80 {
				if len(hexData) < i+80 {
					split = append(split, hexData[i:])
				} else {
					split = append(split, hexData[i:i+80])
				}
			}
		}
	case nil:
		// don't append anything
	default:
		strContent := fmt.Sprintf("%s", content)
		if IndentXML {
			split = append(split, strings.Split(strContent, "\n")...)
		} else {
			split = append(split, strings.ReplaceAll(strContent, "\n", "\\n"))
		}
	}
	if len(split) > 1 && IndentXML {
		for i, line := range split {
			split[i] = "  " + line
		}
	}
	return split
}