// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package streambuf

// ASCII parsing support

import (
	"bytes"
	"errors"
)

var ErrExpectedDigit = errors.New("Expected digit")

// UntilCRLF collects all bytes until a CRLF ("\r\n") sequence is found. The
// returned byte slice will not contain the CRLF sequence.
// If CRLF was not found yet one of ErrNoMoreBytes or ErrUnexpectedEOB will be
// reported.
func (b *Buffer) UntilCRLF() ([]byte, error) {
	if b.err != nil {
		return nil, b.err
	}

	data := b.data[b.offset:]
	for i, byte := range data {
		if byte != '\r' {
			continue
		}

		if len(data) < i+2 {
			b.offset += i
			return nil, b.bufferEndError()
		}

		if data[i+1] != '\n' {
			// false alarm, continue
			continue
		}

		// yay, found
		end := i
		data = b.data[b.mark : b.offset+end]
		b.Advance(len(data) + 2)
		return data, nil
	}

	b.offset += len(data)
	return nil, b.bufferEndError()
}

// IgnoreSymbol will advance the read pointer until the first symbol not
// matching s is found.
func (b *Buffer) IgnoreSymbol(s uint8) error {
	if b.err != nil {
		return b.err
	}

	data := b.data[b.offset:]
	for i, byte := range data {
		if byte != s {
			b.Advance(b.offset + i - b.mark)
			return nil
		}
	}
	b.offset += len(data)
	return b.bufferEndError()
}

// IgnoreSymbols will advance the read pointer until the first symbol not matching
// set of symbols is found
func (b *Buffer) IgnoreSymbols(syms []byte) error {
	if b.err != nil {
		return b.err
	}

	data := b.data[b.offset:]
	for i, byte := range data {
		for _, other := range syms {
			if byte == other {
				goto next
			}
		}
		// no match
		b.Advance(b.offset + i - b.mark)
		return nil

	next:
	}
	b.offset += len(data)
	return b.bufferEndError()
}

// UntilSymbol collects all bytes until symbol s is found. If errOnEnd is set to
// true, the collected byte slice will be returned if no more bytes are available
// for parsing, but s has not matched yet.
func (b *Buffer) UntilSymbol(s uint8, errOnEnd bool) ([]byte, error) {
	if b.err != nil {
		return nil, b.err
	}

	data := b.data[b.offset:]
	for i, byte := range data {
		if byte == s {
			data := b.data[b.mark : b.offset+i]
			b.Advance(len(data))
			return data, nil
		}
	}

	if errOnEnd {
		b.offset += len(data)
		return nil, b.bufferEndError()
	}

	data = b.data[b.mark:]
	b.Advance(len(data))
	return data, nil
}

// UintASCII will parse unsigned number from Buffer.
func (b *Buffer) UintASCII(errOnEnd bool) (uint64, error) {
	if b.err != nil {
		return 0, b.err
	}
	if len(b.data) <= b.mark { // end of buffer
		return 0, b.bufferEndError()
	}

	end, err := b.asciiFindNumberEnd(b.offset, errOnEnd)
	if err != nil {
		return 0, err
	}

	// parse value
	value, err := doParseNumber(b.data[b.mark:end])
	if err != nil {
		return 0, err
	}
	b.Advance(end - b.mark)
	return value, nil
}

// IntASCII will parse (optionally) signed number from Buffer.
func (b *Buffer) IntASCII(errOnEnd bool) (int64, error) {
	if b.err != nil {
		return 0, b.err
	}
	if len(b.data) <= b.mark { // end of buffer
		return 0, b.bufferEndError()
	}

	// check signedness of number
	signed := b.data[b.mark] == '-'
	start := b.mark
	if signed {
		start++
		if len(b.data) <= start {
			return 0, b.bufferEndError()
		}
	} else if b.data[b.mark] == '+' {
		start++
		if len(b.data) <= start {
			return 0, b.bufferEndError()
		}
	}

	// adapt offset to point to start of number
	offset := b.offset
	if b.offset == b.mark {
		offset = start
	}

	end, err := b.asciiFindNumberEnd(offset, errOnEnd)
	if err != nil {
		return 0, err
	}

	value, err := doParseNumber(b.data[start:end])
	if err != nil {
		return 0, err
	}

	b.Advance(end - b.mark)
	if signed {
		return -int64(value), nil
	}
	return int64(value), nil
}

// MatchASCII checks the Buffer it's next byte sequence matched prefix. The
// read pointer is not advanced by AsciiPrefix.
func (b *Buffer) MatchASCII(prefix []byte) (bool, error) {
	if b.err != nil {
		return false, b.err
	}
	if !b.Avail(len(prefix)) {
		return false, b.bufferEndError()
	}

	has := bytes.HasPrefix(b.data[b.mark:], prefix)
	return has, nil
}

func (b *Buffer) asciiFindNumberEnd(start int, errOnEnd bool) (int, error) {
	// find end of number
	end := -1
	for i, byte := range b.data[start:] {
		if byte < '0' || '9' < byte {
			end = i + start
			break
		}
	}

	// check end
	if end < 0 {
		if errOnEnd {
			return -1, b.bufferEndError()
		}
		end = len(b.data)
	}

	return end, nil
}

func doParseNumber(buf []byte) (uint64, error) {
	if len(buf) == 0 {
		return 0, ErrExpectedDigit
	}

	var value uint64
	for _, byte := range buf {
		value = uint64(byte-'0') + 10*value
	}
	return value, nil
}

// binary parsing support
