package main

import (
	"path/filepath"
	"regexp"
	"strings"
)

func ircToLower(c byte) byte {
	switch c {
	case '[':
		return '{'
	case ']':
		return '}'
	case '\\':
		return '|'
	case '~':
		return '^'
	}
	if c >= 'A' && c <= 'Z' {
		return c + ('a' - 'A')
	}
	return c
}

func ircToUpper(c byte) byte {
	switch c {
	case '{':
		return '['
	case '}':
		return ']'
	case '|':
		return '\\'
	case '^':
		return '~'
	}
	if c >= 'a' && c <= 'z' {
		return c - ('a' - 'A')
	}
	return c
}

// Convert identifier to a canonical form for case-insensitive comparisons.
// ircToUpper is used so that statically initialized maps can be in uppercase.
func ircToCanon(ident string) string {
	var canon []byte
	for _, c := range []byte(ident) {
		canon = append(canon, ircToUpper(c))
	}
	return string(canon)
}

func ircEqual(s1, s2 string) bool {
	return ircToCanon(s1) == ircToCanon(s2)
}

func ircFnmatch(pattern string, s string) bool {
	pattern, s = ircToCanon(pattern), ircToCanon(s)
	// FIXME: This should not support [] ranges and handle '/' specially.
	// We could translate the pattern to a regular expression.
	matched, _ := filepath.Match(pattern, s)
	return matched
}

var reMsg = regexp.MustCompile(
	`^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)

type message struct {
	tags    map[string]string // IRC 3.2 message tags
	nick    string            // optional nickname
	user    string            // optional username
	host    string            // optional hostname or IP address
	command string            // command name
	params  []string          // arguments
}

func ircUnescapeMessageTag(value string) string {
	var buf []byte
	escape := false
	for i := 0; i < len(value); i++ {
		if escape {
			switch value[i] {
			case ':':
				buf = append(buf, ';')
			case 's':
				buf = append(buf, ' ')
			case 'r':
				buf = append(buf, '\r')
			case 'n':
				buf = append(buf, '\n')
			default:
				buf = append(buf, value[i])
			}
			escape = false
		} else if value[i] == '\\' {
			escape = true
		} else {
			buf = append(buf, value[i])
		}
	}
	return string(buf)
}

func ircParseMessageTags(tags string, out map[string]string) {
	for _, tag := range strings.Split(tags, ";") {
		if tag == "" {
			// Ignore empty.
		} else if equal := strings.IndexByte(tag, '='); equal < 0 {
			out[tag] = ""
		} else {
			out[tag[:equal]] = ircUnescapeMessageTag(tag[equal+1:])
		}
	}
}

func ircParseMessage(line string) *message {
	m := reMsg.FindStringSubmatch(line)
	if m == nil {
		return nil
	}

	msg := message{nil, m[2], m[3], m[4], m[5], nil}
	if m[1] != "" {
		msg.tags = make(map[string]string)
		ircParseMessageTags(m[1], msg.tags)
	}
	for _, x := range reArgs.FindAllString(m[6], -1) {
		msg.params = append(msg.params, x[1:])
	}
	return &msg
}