package fasthttp

import (
	"bytes"
	"errors"
	"fmt"
)

type headerScanner struct {
	initialized bool

	b []byte
	r int

	key   []byte
	value []byte

	err error
}

func (s *headerScanner) next() bool {
	if !s.initialized {
		if bytes.HasPrefix(s.b, strCRLF) {
			s.r = 2
			return false
		}

		i := bytes.Index(s.b, strCRLFCRLF)
		if i < 0 {
			s.err = errNeedMore
			return false
		}
		i += 4

		s.b = s.b[:i]
		if len(s.b) > 0 && (s.b[0] == ' ' || s.b[0] == '\t') {
			s.err = errors.New("invalid headers, headers cannot start with space or tab")
			return false
		}

		s.initialized = true
	}

	kv, err := s.readContinuedLineSlice()
	if len(kv) == 0 {
		s.err = err
		return false
	}

	// Key ends at first colon.
	k, v, ok := bytes.Cut(kv, strColon)
	if !ok {
		s.err = fmt.Errorf("malformed MIME header line: %q", kv)
		return false
	}
	if !isValidHeaderKey(k) {
		s.err = fmt.Errorf("malformed MIME header line: %q", kv)
		return false
	}

	// Skip initial spaces in value.
	v = bytes.TrimLeft(v, " \t")

	s.key = k
	s.value = v

	if err != nil {
		s.err = err
		return false
	}

	return true
}

// readLine reads a line from b, starting at s.r, and returns it.
func (s *headerScanner) readLine() (line []byte) {
	searchStart := 0

	for {
		if i := bytes.IndexByte(s.b[s.r+searchStart:], '\n'); i >= 0 {
			i += searchStart
			line = s.b[s.r : s.r+i+1]
			s.r += i + 1
			break
		}

		searchStart = len(s.b) - s.r
	}

	if len(line) == 0 {
		return
	}

	// drop \n and possible preceding \r
	if line[len(line)-1] == '\n' {
		drop := 1
		if len(line) > 1 && line[len(line)-2] == '\r' {
			drop = 2
		}
		line = line[:len(line)-drop]
	}
	return
}

// readContinuedLineSlice reads continued lines from b until it finds a line
// that does not start with a space or tab, or it reaches the end of b.
func (s *headerScanner) readContinuedLineSlice() ([]byte, error) {
	line := s.readLine()
	if len(line) == 0 { // blank line - no continuation
		return line, nil
	}

	if bytes.IndexByte(line, ':') < 0 {
		return nil, fmt.Errorf("malformed MIME header: missing colon: %q", line)
	}

	// If the line doesn't start with a space or tab, we are done.
	if len(s.b)-s.r > 1 {
		peek := s.b[s.r : s.r+2]
		if len(peek) > 0 && (isASCIILetter(peek[0]) || peek[0] == '\n') ||
			len(peek) == 2 && peek[0] == '\r' && peek[1] == '\n' {
			return trim(line), nil
		}
	}

	mline := trim(line)

	// Read continuation lines.
	for s.skipSpace() {
		mline = append(mline, ' ')
		line := s.readLine()
		mline = append(mline, trim(line)...)
	}
	return mline, nil
}

// skipSpace skips one or multiple spaces and tabs in b.
func (s *headerScanner) skipSpace() bool {
	skipped := false
	for {
		c := s.b[s.r]
		if c != ' ' && c != '\t' {
			break
		}
		s.r++
		skipped = true
	}
	return skipped
}

func isASCIILetter(b byte) bool {
	b |= 0x20 // Make lower case.
	return 'a' <= b && b <= 'z'
}

// trim returns s with leading and trailing spaces and tabs removed.
// It does not assume Unicode or UTF-8.
func trim(s []byte) []byte {
	i := 0
	for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
		i++
	}
	n := len(s)
	for n > i && (s[n-1] == ' ' || s[n-1] == '\t') {
		n--
	}
	return s[i:n]
}
