package circuitcompiler

import (
	"bufio"
	"bytes"
	"io"
)

type OperatorSymbol int
type Token int

const (
	ILLEGAL Token = 1 << iota
	WS
	EOF
	FUNC
	IDENT // val
	IN
	VAR      // var
	CONST    // const value
	EQ       // =
	PLUS     // +
	MINUS    // -
	MULTIPLY // *
	DIVIDE   // /
	EXP      // ^
	OUT
)

func (ch Token) String() string {
	switch ch {
	case EQ:
		return "="
	case PLUS:
		return "+"
	case MINUS:
		return "-"
	case MULTIPLY:
		return "*"
	case DIVIDE:
		return "/"
	case EXP:
		return "^"
	case FUNC:
		return "def"
	case IN:
		return "In"
	case CONST:
		return "Const"
	default:
		return "unknown Token"

	}

}

var eof = rune(0)

func isWhitespace(ch rune) bool {
	return ch == ' ' || ch == '\t' || ch == '\n'
}

func isLetter(ch rune) bool {
	return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}
func isDigit(ch rune) bool {
	return (ch >= '0' && ch <= '9')
}

// Scanner holds the bufio.Reader
type Scanner struct {
	r *bufio.Reader
}

// NewScanner creates a new Scanner with the given io.Reader
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()
}

// Scan returns the Token and literal string of the current value
func (s *Scanner) scan() (tok Token, lit string) {
	ch := s.read()

	if isWhitespace(ch) {
		// space
		s.unread()
		return s.scanWhitespace()
	} else if isLetter(ch) {
		// letter
		s.unread()
		return s.scanIndent()
	} else if isDigit(ch) {
		s.unread()
		return s.scanIndent()
	}

	switch ch {
	case eof:
		return EOF, ""
	case '=':
		return EQ, "="
	case '+':
		return PLUS, "+"
	case '-':
		return MINUS, "-"
	case '*':
		return MULTIPLY, "*"
	case '/':
		return DIVIDE, "/"
	case '^':
		return EXP, "^"
		//case '(':
		//	return EXP, "func"
	}

	return ILLEGAL, string(ch)
}

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

	for {
		if ch := s.read(); ch == eof {
			break
		} else if !isWhitespace(ch) {
			s.unread()
			break
		} else {
			_, _ = buf.WriteRune(ch)
		}
	}
	return WS, buf.String()
}

func (s *Scanner) scanIndent() (tok Token, lit string) {
	var buf bytes.Buffer
	buf.WriteRune(s.read())
	tok = IDENT
	for {
		if ch := s.read(); ch == eof {
			break
		} else if ch == '(' {
			tok = FUNC
			_, _ = buf.WriteRune(ch)
		} else if ch == ',' && tok == FUNC {
			_, _ = buf.WriteRune(ch)
		} else if isWhitespace(ch) && tok == FUNC {
			continue
		} else if ch == ')' && tok == FUNC {
			_, _ = buf.WriteRune(ch)
			break
		} else if !isLetter(ch) && !isDigit(ch) {
			s.unread()
			break
		} else {
			_, _ = buf.WriteRune(ch)
		}
	}

	switch buf.String() {
	case "var":
		return VAR, buf.String()
	}

	if len(buf.String()) == 1 {
		return Token(rune(buf.String()[0])), buf.String()
	}
	if buf.String() == "out" {
		return OUT, buf.String()
	}
	return IDENT, buf.String()
}
