// Copyright 2013 Jonas mg
//
// 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 shconf

import (
	"bufio"
	"bytes"
	"io"
	"strconv"
	"unicode"
	"unicode/utf8"
)

type extraCharError int

func (e extraCharError) Error() string {
	return strconv.Itoa(int(e)) + ": extra character/s after of the value"
}

type keyError int

func (e keyError) Error() string {
	return strconv.Itoa(int(e)) + ": key not found"
}

type noASCIIKeyError int

func (e noASCIIKeyError) Error() string {
	return strconv.Itoa(int(e)) + ": the key only must to have ASCII characters"
}

type invalidCharError int

func (e invalidCharError) Error() string {
	return strconv.Itoa(int(e)) + ": key with invalid character"
}

type openQuoteError int

func (e openQuoteError) Error() string {
	return strconv.Itoa(int(e)) + ": the quote in the value is not closed"
}

type valueError int

func (e valueError) Error() string {
	return strconv.Itoa(int(e)) + ": value not found"
}

// * * *

// separator is the character used like separator, by default.
var separator = []byte{'='}

// Scanner provides a convenient interface for reading data such as a file of
// lines of text in format key-value. Successive calls to the Scan method will
// step through the 'tokens' of a file, skipping the bytes between the tokens.
//
// The valid characters for the keys are: [a-zA-Z_.]
//
// Scanning stops unrecoverably at EOF, the first I/O error, or a token too
// large to fit in the buffer.
type Scanner struct {
	buf *bufio.Reader

	// Character/s used to separate the value from key.
	// It is only get in the first call to "Scan()".
	separator []byte

	key   []byte
	value []byte

	err  error
	line int // Number of line being scanned
}

// NewScanner returns a new Scanner to read from r, with the option to skip the
// keys disabled.
func NewScanner(r io.Reader) *Scanner { return &Scanner{buf: bufio.NewReader(r)} }

// Scan advances the Scanner to the next tokens, which will then be available
// through the Bytes or Text method. It returns false when the scan stops, either
// by reaching the end of the input or an error. After Scan returns false, the Err
// method will return any error that occurred during scanning, except that if it
// was io.EOF, Err will return nil.
func (s *Scanner) Scan() bool {
	var thisRune rune
	var n int
	var err error

	for s.line++; ; s.line++ {
		// Skip leading spaces.
		for thisRune, n, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
			if err != nil {
				s.err = err
				return false
			}

			if thisRune == '\n' {
				s.line++
				continue
			}
			if !unicode.IsSpace(thisRune) {
				break
			}
		}

		// Skip line comment and section.
		if thisRune == '#' || thisRune == '[' {
			if _, err = s.buf.ReadBytes('\n'); err != nil {
				s.err = err
				return false
			}
			continue
		}
		break
	}

	if thisRune == '=' {
		s.err = keyError(s.line)
		return false
	}

	var key, value, separator []rune

	// Get key

	for ; ; thisRune, n, err = s.buf.ReadRune() {
		if err != nil {
			if err != io.EOF {
				s.err = err
			} else {
				s.err = valueError(s.line)
			}
			return false
		}

		// The separator charater could the the equal sign or space.
		if thisRune == '=' || unicode.IsSpace(thisRune) {
			break
		}

		if n > 1 {
			s.err = noASCIIKeyError(s.line)
			return false
		}
		if !isValidChar(thisRune) {
			s.err = invalidCharError(s.line)
			return false
		}
		key = append(key, thisRune)
	}

	// Skip spaces before and after of the separator character.

	if unicode.IsSpace(thisRune) {
		separator = append(separator, thisRune)

		for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
			if err != nil {
				if err != io.EOF {
					s.err = err
				} else {
					s.err = valueError(s.line)
				}
				return false
			}

			if !unicode.IsSpace(thisRune) {
				break
			}
			separator = append(separator, thisRune)
		}
	}
	if thisRune == '=' {
		separator = append(separator, thisRune)

		if thisRune, _, err = s.buf.ReadRune(); err != nil {
			if err != io.EOF {
				s.err = err
			} else {
				s.err = valueError(s.line)
			}
			return false
		}
	}
	if unicode.IsSpace(thisRune) {
		separator = append(separator, thisRune)

		for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
			if err != nil {
				if err != io.EOF {
					s.err = err
				} else {
					s.err = valueError(s.line)
				}
				return false
			}

			if !unicode.IsSpace(thisRune) {
				break
			}
			separator = append(separator, thisRune)
		}
	}

	// Get value

	var valueIsString, valueInDQuote bool
	var lastRune rune

	if thisRune == '"' { // The value is between double quotes
		valueIsString = true
		valueInDQuote = true
	} else if thisRune == '\'' { // between single quotes
		valueIsString = true
	} else {
		value = append(value, thisRune)
	}

	for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
		if err != nil {
			if err != io.EOF {
				s.err = err
				return false
			}

			if valueIsString {
				s.err = openQuoteError(s.line)
				return false
			}
			s.err = err
			break
		}

		if valueIsString {
			if valueInDQuote {
				if thisRune == '"' && lastRune != '\\' {
					break
				}
			} else if thisRune == '\'' && lastRune != '\\' {
				break
			}
			lastRune = thisRune // To checking if it is a quote escaped.
			value = append(value, thisRune)

		} else {
			if unicode.IsSpace(thisRune) {
				break
			}
			value = append(value, thisRune)
		}
	}

	// Sanity check
	if thisRune != '\n' && thisRune != '\r' {
		doCheck := true
		last, _, err := s.buf.ReadLine()
		if err != nil {
			if err != io.EOF {
				s.err = err
				return false
			} else {
				doCheck = false
			}
		}

		if doCheck {
			for _, char := range last {
				if unicode.IsSpace(rune(char)) {
					continue
				} else if char == '#' {
					break
				} else {
					s.err = extraCharError(s.line)
					return false
				}
			}
		}
	}

	// Store key and value

	var bufKey, bufValue, bufSep bytes.Buffer
	var bytes []byte

	for _, r := range key {
		bytes = make([]byte, utf8.RuneLen(r))
		utf8.EncodeRune(bytes, r)
		bufKey.Write(bytes)
	}
	s.key = bufKey.Bytes()

	for _, r := range value {
		bytes = make([]byte, utf8.RuneLen(r))
		utf8.EncodeRune(bytes, r)
		bufValue.Write(bytes)
	}
	s.value = bufValue.Bytes()

	// Store separator character
	if s.separator == nil {
		for _, r := range separator {
			bytes = make([]byte, utf8.RuneLen(r))
			utf8.EncodeRune(bytes, r)
			bufSep.Write(bytes)
		}
		s.separator = bufSep.Bytes()
	}

	return true
}

// Bytes returns the most recents tokens generated by a call to Scan. The
// underlying array may point to data that will be overwritten by a subsequent
// call to Scan. It does no allocation.
func (s *Scanner) Bytes() (key, value []byte) { return s.key, s.value }

// Text returns the most recents tokens generated by a call to Scan as a newly
// allocated string holding its bytes.
func (s *Scanner) Text() (key, value string) {
	return string(s.key), string(s.value)
}

// Err returns the first non-EOF error that was encountered by the Scanner.
func (s *Scanner) Err() error {
	if s.err != io.EOF {
		return s.err
	}
	return nil
}

// Separator returns the character/s used to separate the key from the value.
//
// The separator is got in the first call to "Scan()"; if it has not been
// called, this makes it explicitly but panics when there is any error.
func (s *Scanner) Separator() []byte {
	if s.separator == nil {
		if found := s.Scan(); !found {
			if err := s.Err(); err != nil {
				panic(err)
			}
			return separator
		}
	}
	return s.separator
}

// == Utility

func isValidChar(r rune) bool {
	if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
		return true
	}
	switch r {
	case '_', '.':
		return true
	default:
		return false
	}
}
