package parser

import (
	"fmt"
	"strings"
)

// QuoteString walks characters (after trimming), escapes any quotes and
// escapes, then wraps the whole thing in quotes. Very useful for generating
// argument output in nodes.
func QuoteString(str string) string {
	result := ""
	chars := strings.Split(strings.TrimSpace(str), "")

	for _, char := range chars {
		switch char {
		case `"`:
			result += `\"`
		case `\`:
			result += `\\`
		default:
			result += char
		}
	}

	return `"` + result + `"`
}

// dumps the AST defined by `node` as a list of sexps. Returns a string
// suitable for printing.
func (node *Node) Dump() string {
	str := ""
	str += node.Value

	for _, n := range node.Children {
		str += "(" + n.Dump() + ")\n"
	}

	if node.Next != nil {
		for n := node.Next; n != nil; n = n.Next {
			if len(n.Children) > 0 {
				str += " " + n.Dump()
			} else {
				str += " " + QuoteString(n.Value)
			}
		}
	}

	return strings.TrimSpace(str)
}

// performs the dispatch based on the two primal strings, cmd and args. Please
// look at the dispatch table in parser.go to see how these dispatchers work.
func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
	fn := dispatch[cmd]

	// Ignore invalid Dockerfile instructions
	if fn == nil {
		fn = parseIgnore
	}

	sexp, attrs, err := fn(args)
	if err != nil {
		return nil, nil, err
	}

	return sexp, attrs, nil
}

// splitCommand takes a single line of text and parses out the cmd and args,
// which are used for dispatching to more exact parsing functions.
func splitCommand(line string) (string, string, error) {
	cmdline := TOKEN_WHITESPACE.Split(line, 2)

	if len(cmdline) != 2 {
		return "", "", fmt.Errorf("We do not understand this file. Please ensure it is a valid Dockerfile. Parser error at %q", line)
	}

	cmd := strings.ToLower(cmdline[0])
	// the cmd should never have whitespace, but it's possible for the args to
	// have trailing whitespace.
	return cmd, strings.TrimSpace(cmdline[1]), nil
}

// covers comments and empty lines. Lines should be trimmed before passing to
// this function.
func stripComments(line string) string {
	// string is already trimmed at this point
	if TOKEN_COMMENT.MatchString(line) {
		return TOKEN_COMMENT.ReplaceAllString(line, "")
	}

	return line
}