/*
 * ECAL
 *
 * Copyright 2020 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the MIT
 * License, If a copy of the MIT License was not distributed with this
 * file, You can obtain one at https://opensource.org/licenses/MIT.
 */

package parser

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"testing"
)

// Main function for all tests in this package

func TestMain(m *testing.M) {
	flag.Parse()

	res := m.Run()

	// Check if all nodes have been tested

	for _, n := range astNodeMap {
		if _, ok := usedNodes[n.Name]; !ok {
			fmt.Println("Not tested node: ", n.Name)
		}
	}

	// Check if all nodes have been pretty printed

	for k := range prettyPrinterMap {
		if _, ok := usedPrettyPrinterNodes[k]; !ok {
			fmt.Println("Not tested pretty printer: ", k)
		}
	}

	os.Exit(res)
}

// Used nodes map which is filled during unit testing. Prefilled with tokens which
// will not be generated by the parser
//
var usedNodes = map[string]bool{
	NodeEOF: true, // Only used as end term
	"":      true, // No node e.g. semicolon - These nodes should never be part of an AST
}

func UnitTestParse(name string, input string) (*ASTNode, error) {
	return UnitTestParseWithPPResult(name, input, "")
}

func UnitTestParseWithPPResult(name string, input string, expectedPPRes string) (*ASTNode, error) {
	n, err := ParseWithRuntime(name, input, &DummyRuntimeProvider{})

	// Test AST serialization

	if err == nil {
		var unmarshaledJSONObject map[string]interface{}

		astString, err := json.Marshal(n.ToJSONObject())
		if err != nil {
			return nil, fmt.Errorf("Could not marshal AST: %v", err)
		}

		if err := json.Unmarshal(astString, &unmarshaledJSONObject); err != nil {
			return nil, fmt.Errorf("Could not unmarshal JSON object: %v", err)
		}

		unmarshaledAST, err := ASTFromJSONObject(unmarshaledJSONObject)
		if err != nil {
			return nil, fmt.Errorf("Could not create AST from unmarshaled JSON object: %v", err)
		}

		// Compare the ASTs

		if ok, msg := n.Equals(unmarshaledAST, false); !ok {
			return nil, fmt.Errorf(
				"Parsed AST is different from the unmarshaled AST."+
					"\n%v\n", msg)
		}
	}

	// Test Pretty printing

	if err == nil {

		ppres, err := PrettyPrint(n)
		if err != nil {
			return nil, fmt.Errorf("Error while pretty printing: %v (input: %v)", err, input)
		}

		if expectedPPRes == "" {

			n2, err := ParseWithRuntime(name, ppres, &DummyRuntimeProvider{})
			if err != nil {
				return nil, fmt.Errorf("Error while parsing pretty print result: %v (result: %v)", err, ppres)
			}

			// Compare the ASTs

			if ok, msg := n.Equals(n2, true); !ok {
				return nil, fmt.Errorf(
					"Parsed AST from pretty printer is different from the originally parsed AST."+
						"\nOriginal input: %v\nPretty printed: %v\nPretty AST: %v\n%v\n",
					input, ppres, n2, msg)
			}

		} else if ppres != expectedPPRes {

			return nil, fmt.Errorf("Expected pretty printer result is different:\nExpected "+
				"result: %v\nActual result: %v\n", expectedPPRes, ppres)
		}

		markASTNodesAsPrettyPrinted(n)
	}

	return n, err
}

// Used nodes map which is filled during unit testing. Prefilled with tokens which
// will not be generated by the parser
//
var usedPrettyPrinterNodes = map[string]bool{}

func markASTNodesAsPrettyPrinted(n *ASTNode) {

	// Make the encountered node as used

	numChildren := len(n.Children)
	if numChildren > 0 {
		usedPrettyPrinterNodes[fmt.Sprintf("%v_%v", n.Name, numChildren)] = true
	} else {
		usedPrettyPrinterNodes[n.Name] = true
	}

	for _, c := range n.Children {
		markASTNodesAsPrettyPrinted(c)
	}
}

func UnitTestPrettyPrinting(input, astOutput, ppOutput string) error {
	astres, err := ParseWithRuntime("mytest", input, &DummyRuntimeProvider{})
	if err != nil || fmt.Sprint(astres) != astOutput {
		return fmt.Errorf("Unexpected parser output:\n%v expected was:\n%v Error: %v", astres, astOutput, err)
	}

	markASTNodesAsPrettyPrinted(astres)

	ppres, err := PrettyPrint(astres)
	if err != nil || ppres != ppOutput {
		return fmt.Errorf("Unexpected result: %v (expected: %v) error: %v", ppres, ppOutput, err)
	}

	// Make sure the pretty printed result is valid and gets the same parse tree

	astres2, err := ParseWithRuntime("mytest", ppres, &DummyRuntimeProvider{})
	if err != nil || fmt.Sprint(astres2) != astOutput {
		return fmt.Errorf("Unexpected parser output from pretty print string:\n%v expected was:\n%v Error: %v", astres2, astOutput, err)
	}

	return nil
}

// Helper objects

type DummyRuntimeProvider struct {
}

func (d *DummyRuntimeProvider) Runtime(n *ASTNode) Runtime {

	// Make the encountered node as used

	usedNodes[n.Name] = true

	return nil
}
