package stressql

import (
	"bufio"
	"bytes"
	"io"
	"log"
	"os"
	"strings"

	"github.com/influxdata/influxdb/stress/v2/statement"
	stressql "github.com/influxdata/influxdb/stress/v2/stressql/statement"
	"github.com/influxdata/influxql"
)

// Token represents a lexical token.
type Token int

// These are the lexical tokens used by the file parser
const (
	ILLEGAL Token = iota
	EOF
	STATEMENT
	BREAK
)

var eof = rune(0)

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func isNewline(r rune) bool {
	return r == '\n'
}

// Scanner scans the file and tokenizes the raw text
type Scanner struct {
	r *bufio.Reader
}

// NewScanner returns a Scanner
func NewScanner(r io.Reader) *Scanner {
	return &Scanner{r: bufio.NewReader(r)}
}

func (s *Scanner) read() rune {
	ch, _, err := s.r.ReadRune()
	if err != nil {
		return eof
	}
	return ch
}

func (s *Scanner) unread() { _ = s.r.UnreadRune() }

func (s *Scanner) peek() rune {
	ch := s.read()
	s.unread()
	return ch
}

// Scan moves the Scanner forward one character
func (s *Scanner) Scan() (tok Token, lit string) {
	ch := s.read()

	if isNewline(ch) {
		s.unread()
		return s.scanNewlines()
	} else if ch == eof {
		return EOF, ""
	} else {
		s.unread()
		return s.scanStatements()
	}
	// golint marks as unreachable code
	// return ILLEGAL, string(ch)
}

func (s *Scanner) scanNewlines() (tok Token, lit string) {
	var buf bytes.Buffer
	buf.WriteRune(s.read())

	for {
		if ch := s.read(); ch == eof {
			break
		} else if !isNewline(ch) {
			s.unread()
			break
		} else {
			buf.WriteRune(ch)
		}
	}

	return BREAK, buf.String()
}

func (s *Scanner) scanStatements() (tok Token, lit string) {
	var buf bytes.Buffer
	buf.WriteRune(s.read())

	for {
		if ch := s.read(); ch == eof {
			break
		} else if isNewline(ch) && isNewline(s.peek()) {
			s.unread()
			break
		} else if isNewline(ch) {
			s.unread()
			buf.WriteRune(ch)
		} else {
			buf.WriteRune(ch)
		}
	}

	return STATEMENT, buf.String()
}

// ParseStatements takes a configFile and returns a slice of Statements
func ParseStatements(file string) ([]statement.Statement, error) {
	seq := []statement.Statement{}

	f, err := os.Open(file)
	check(err)

	s := NewScanner(f)

	for {
		t, l := s.Scan()

		if t == EOF {
			break
		}
		_, err := influxql.ParseStatement(l)
		if err == nil {

			seq = append(seq, &statement.InfluxqlStatement{Query: l, StatementID: stressql.RandStr(10)})
		} else if t == BREAK {
			continue
		} else {
			f := strings.NewReader(l)
			p := stressql.NewParser(f)
			s, err := p.Parse()
			if err != nil {
				return nil, err
			}
			seq = append(seq, s)

		}
	}

	f.Close()

	return seq, nil

}
