package resolvconf

import (
	"fmt"
	"github.com/hashicorp/go-multierror"
	"io"
	"io/ioutil"
	"net"
	"strconv"
	"strings"
)

func parseOption(o string) (*Option, error) {
	keyval := strings.Split(o, ":")

	switch opt := keyval[0]; opt {
	case "debug", "rotate", "no-check-names", "inet6",
		"ip6-bytestring", "ip6-dotint", "no-ip6-dotint",
		"edns0", "single-request", "single-request-reopen",
		"no-tld-query", "use-vc":
		return &Option{o, -1}, nil
	case "ndots", "timeout", "attempts":
		val, err := strconv.Atoi(keyval[1])
		if err != nil {
			return nil, fmt.Errorf("%s unable to parse option value %s", opt, keyval[1])
		}
		return &Option{opt, val}, nil
	default:
		return nil, fmt.Errorf("Unknown option %s", opt)
	}
}

func parseLine(line string) ([]ConfItem, error) {
	toks := strings.Fields(line)
	var items []ConfItem
	var err error
	switch keyword := toks[0]; keyword {
	case "nameserver":
		ns := new(Nameserver)
		if ns.IP = net.ParseIP(toks[1]); ns.IP == nil {
			err = fmt.Errorf("Malformed IP address: %s", toks[1])
			break
		}
		items = append(items, ns)
	case "domain":
		items = append(items, NewDomain(toks[1]))
	case "search":
		for _, dom := range toks[1:] {
			items = append(items, NewSearchDomain(dom))
		}
	case "sortlist":
		for _, pair := range toks[1:] {
			var addr, nm net.IP
			addrNmStr := strings.Split(pair, "/")
			if addr = net.ParseIP(addrNmStr[0]); addr == nil {
				err = fmt.Errorf("Malformed IP address %s in searchlist", pair)
				break
			}
			if len(addrNmStr) > 1 {
				if nm = net.ParseIP(addrNmStr[1]); nm == nil {
					err = fmt.Errorf("Malformed netmask %s in searchlist", pair)
					break
				}
			}
			items = append(items, NewSortItem(addr).SetNetmask(nm))
		}
	case "options":
		for _, optStr := range toks[1:] {
			opt, e := parseOption(optStr)
			if e != nil {
				err = e
				break
			}
			items = append(items, opt)
		}
	default:
		err = fmt.Errorf("Unknown keyword %s", keyword)
	}

	return items, err
}

// ReadConf will read a configuration from given io.Reader
//
// Returns a new Conf object when successful otherwise
// nil and an error
func ReadConf(r io.Reader) (*Conf, error) {
	var res *multierror.Error
	conf := New()
	b, err := ioutil.ReadAll(r)
	if err != nil {
		res = multierror.Append(res, err)
		return nil, res
	}
	confFile := strings.TrimSpace(string(b[:]))
	lines := strings.Split(confFile, "\n")
	for _, line := range lines {
		// Check if this line is a comment or empty
		if len(line) == 0 || line[0] == byte('#') || line[0] == byte(';') {
			continue
		}
		// Otherwise decode line
		opt, err := parseLine(line)
		if err != nil {
			res = multierror.Append(res, err)
			continue
		}

		for _, o := range opt {
			if err := conf.Add(o); err != nil {
				res = multierror.Append(res, err)
			}
		}
	}
	return conf, res.ErrorOrNil()
}