package main

import (
	"bytes"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// Generate a regex accepting any string but the string of concatenated numbers
// lower_bound to upper_bound (each inclusive).
func generate_regex(lower_bound, upper_bound, mode int) string {
	var not_str bytes.Buffer
	var buf bytes.Buffer

	// generate not_string
	for number := lower_bound; number <= upper_bound; number++ {
		not_str.WriteString(strconv.Itoa(number))
	}
	not_string := not_str.String()
	l := len(not_string)

	// mode 3 = lookahead
	if mode == 3 {
		buf.WriteString("^(?!")
		buf.WriteString(not_string)
		buf.WriteString("$).*$")
		return buf.String()
	}

	buf.WriteString("^(")

	// strings with length smaller than not_string
	// mode 1 == |.|..|...|…
	if mode == 1 {
		for i := 0; i < l; i++ {
			buf.WriteString(strings.Repeat(".", i))
			if i != l-1 {
				buf.WriteString("|")
			}
		}
		// mode 2 == .?.?.?.?…
	} else if mode == 2 {
		buf.WriteString(strings.Repeat(".?", l-1))
	}

	buf.WriteString("|")

	// strings with length equal to not_string
	for index := 0; index < l; index++ {
		buf.WriteString(strings.Repeat(".", index))
		buf.WriteString("[^")
		buf.WriteString(string(not_string[index]))
		buf.WriteString("]")
		buf.WriteString(strings.Repeat(".", l-index-1))
		if index != l-1 {
			buf.WriteString("|")
		}
	}

	buf.WriteString("|")

	// strings with length greater than not_string

	buf.WriteString(strings.Repeat(".", l))
	buf.WriteString(".+")

	buf.WriteString(")$")

	return buf.String()
}

// Generate less than `(upper_bound - lower_bound)^2` input strings.
// Returns testsuite dict.
func generate_input_strings(lower_bound, upper_bound int) map[string]bool {
	testsuite := make(map[string]bool)

	// generate not_string
	var not_str bytes.Buffer
	for number := lower_bound; number <= upper_bound; number++ {
		not_str.WriteString(strconv.Itoa(number))
	}
	not_string := not_str.String()

	// generate strings
	for begin := lower_bound; begin <= upper_bound; begin++ {
		for end := begin + 1; end <= upper_bound; end++ {
			var input_string = not_string[begin:end]
			matches := (input_string != not_string)
			testsuite[input_string] = matches
		}
	}

	return testsuite
}

// A helper for measuring time
func trackingTime(begin time.Time) {
	elapsed := time.Since(begin)
	fmt.Printf("It takes %s to test all input strings.", elapsed)
}

// Actually run all pattern searches.
func run_test(pat *regexp.Regexp, testsuite map[string]bool) {
	defer trackingTime(time.Now())

	for input, match := range testsuite {
		//mat := pat.FindStringIndex(input)
		//matches := (mat != nil)
        matches := pat.MatchString(input)

		if match != matches {
			fmt.Printf("FAIL for %s (should be %t)\n", input, matches)
			return
		}
	}
}

func main() {
	low := 1
	upp := 500

	regex := generate_regex(low, upp, 1)
    fmt.Println(regex)
    fmt.Println("\n\n\n\n")
	testsuite := generate_input_strings(low, upp)

	fmt.Printf("Generated regex of string length %d.\n", len(regex))
	fmt.Printf("Generated %d input strings.\n", len(testsuite))

	pat := regexp.MustCompile(regex)

	fmt.Println("Regex compilation has finished.\n")

	run_test(pat, testsuite)

	os.Exit(0)
}