package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"

	"github.com/johnkerl/pgpg/apps/go/manual/lexers"
	liblexers "github.com/johnkerl/pgpg/go/lib/pkg/lexers"

	generatedlexers "github.com/johnkerl/pgpg/apps/go/generated/pkg/lexers"
)

type lexerMaker func(io.Reader) liblexers.AbstractLexer

type lexerInfoT struct {
	maker lexerMaker
	help  string
}

var lexerMakerTable = map[string]lexerInfoT{
	"m:canned":       lexerInfoT{lexers.NewCannedTextLexer, "Does string-split on the input at startup."},
	"m:rune":         lexerInfoT{lexers.NewRuneLexer, "Each UTF-8 character is its own token."},
	"m:line":         lexerInfoT{lexers.NewLineLexer, "Each line of text is its own token. Carriage returns are not delivered."},
	"m:word":         lexerInfoT{lexers.NewWordLexer, "Each run of non-whitespace text is its own token. Whitespace is not delivered."},
	"m:seng":         lexerInfoT{lexers.NewSENGLexer, "SENG lexicon."},
	"m:am":           lexerInfoT{lexers.NewAMLexer, "Integers with + and *."},
	"m:pemdas":       lexerInfoT{lexers.NewPEMDASLexer, "Arithmetic with parentheses and PEMDAS operators."},
	"m:vic":          lexerInfoT{lexers.NewVICLexer, "Arithmetic with identifiers, assignments, and PEMDAS operators."},
	"m:vbc":          lexerInfoT{lexers.NewVBCLexer, "Boolean expressions with identifiers and AND/OR/NOT."},
	"m:ebnf":         lexerInfoT{liblexers.NewEBNFLexer, "EBNF grammar with identifiers, literals, and operators."},
	"g:signd":        lexerInfoT{generatedlexers.NewSignDigitLexer, "Generated sign/digit lexer from apps/bnfs/sign-digit.bnf."},
	"g:pemdas-plain": lexerInfoT{generatedlexers.NewPEMDASPlainLexer, "Generated PEMDAS lexer from apps/bnfs/pemdas-plain.bnf."},
	"g:pemdas":       lexerInfoT{generatedlexers.NewPEMDASLexer, "Generated PEMDAS hinted lexer from apps/bnfs/pemdas.bnf."},
	"g:stmts":        lexerInfoT{generatedlexers.NewStatementsLexer, "Generated statements lexer from apps/bnfs/statements.bnf."},
	"g:seng":         lexerInfoT{generatedlexers.NewSENGLexer, "Generated statements lexer from apps/bnfs/seng.bnf."},
	"g:lisp":         lexerInfoT{generatedlexers.NewLISPLexer, "Generated LISP lexer from apps/bnfs/lisp.bnf."},
	"g:json":         lexerInfoT{generatedlexers.NewJSONLexer, "Generated JSON lexer from apps/bnfs/json.bnf."},
	"g:json-plain":   lexerInfoT{generatedlexers.NewJSONPlainLexer, "Generated JSON lexer from apps/bnfs/json_plain.bnf."},
}

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [options] {lexer name} [file ...]\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  With -e (before lexer name): one or more positional args are expressions (error if none).\n")
	fmt.Fprintf(os.Stderr, "  Without -e: zero args = read from stdin; one or more = read from those files.\n")
	flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "Lexer names:\n")
	names := make([]string, 0, len(lexerMakerTable))
	for name := range lexerMakerTable {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		maker := lexerMakerTable[name]
		fmt.Fprintf(os.Stderr, "  %-10s %s\n", name, maker.help)
	}
	os.Exit(1)
}

func main() {
	var exprMode bool
	flag.BoolVar(&exprMode, "e", false, "Arguments are expressions to lex (at least one required)")
	flag.Usage = usage
	flag.Parse()

	if flag.NArg() < 1 {
		usage()
	}
	lexerName := flag.Arg(0)
	args := flag.Args()[1:]

	lexerInfo, ok := lexerMakerTable[lexerName]
	if !ok {
		usage()
	}
	lexerMaker := lexerInfo.maker

	if exprMode {
		if len(args) == 0 {
			fmt.Fprintln(os.Stderr, "trylex: -e requires at least one argument")
			os.Exit(1)
		}
		for _, arg := range args {
			if err := runLexerOnce(lexerMaker, strings.NewReader(arg)); err != nil {
				fmt.Fprintln(os.Stderr, err)
				os.Exit(1)
			}
		}
		return
	}
	if len(args) == 0 {
		if err := runLexerOnce(lexerMaker, os.Stdin); err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	} else if err := runLexerOnFiles(lexerMaker, args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func runLexerOnce(lexerMaker lexerMaker, r io.Reader) error {
	lexer := lexerMaker(r)
	return lexers.Run(lexer)
}

func runLexerOnFiles(lexerMaker lexerMaker, filenames []string) error {
	for _, filename := range filenames {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}
		if err := runLexerOnce(lexerMaker, f); err != nil {
			_ = f.Close()
			return err
		}
		if err := f.Close(); err != nil {
			return err
		}
	}
	return nil
}
