// original source: https://github.com/nhoffmann/life by N.Hoffmann 2020.
package rle

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

type RLE struct {
	Rule    string  // rule
	Width   int     // x
	Height  int     // y
	Pattern [][]int // The actual pattern

	inputLines       []string
	headerLineIndex  int
	patternLineIndex int
}

// wrapper to load a RLE file
func GetRLE(filename string) (*RLE, error) {
	if filename == "" {
		return nil, nil
	}

	content, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	parsedRle, err := Parse(string(content))
	if err != nil {
		return nil, fmt.Errorf("failed to load RLE pattern file: %s", err)
	}

	return &parsedRle, nil
}

func Parse(input string) (RLE, error) {
	rle := RLE{
		inputLines: strings.Split(input, "\n"),
	}

	rle.partitionFile()

	err := rle.parseComments()
	if err != nil {
		return RLE{}, err
	}
	err = rle.parseHeader()
	if err != nil {
		return RLE{}, err
	}
	err = rle.parsePattern()
	if err != nil {
		return RLE{}, err
	}

	return rle, nil
}

func (rle *RLE) partitionFile() error {
	for index, line := range rle.inputLines {
		cleanLine := removeWhitespace(line)
		if strings.HasPrefix(cleanLine, "x=") {
			rle.headerLineIndex = index
			rle.patternLineIndex = index + 1
			return nil
		}
	}

	return fmt.Errorf("invalid input: Header is missing")
}

func (rle *RLE) parseComments() error {
	return nil
}

func (rle *RLE) parseHeader() (err error) {
	headerLine := removeWhitespace(rle.inputLines[rle.headerLineIndex])

	headerElements := strings.SplitN(headerLine, ",", 3)

	rle.Width, err = strconv.Atoi(strings.TrimPrefix(headerElements[0], "x="))
	if err != nil {
		return err
	}
	rle.Height, err = strconv.Atoi(strings.TrimPrefix(headerElements[1], "y="))
	if err != nil {
		return err
	}

	rle.Pattern = make([][]int, rle.Width)

	// check wehter a rule is present, since it's optional
	if len(headerElements) == 3 {
		rle.Rule = strings.TrimPrefix(headerElements[2], "rule=")
	}

	return nil
}

func (rle *RLE) parsePattern() error {
	patternString := strings.Join(rle.inputLines[rle.patternLineIndex:], "")

	l := NewLexer(patternString)
	pp := NewParser(l)

	rle.Pattern = pp.ParsePattern(rle.Width, rle.Height)

	return nil
}

func removeWhitespace(input string) string {
	re := regexp.MustCompile(` *\t*\r*\n*`)
	return re.ReplaceAllString(input, "")
}

// Store a grid to an RLE file
func StoreGridToRLE(grid [][]bool, filename, rule string, width, height int) error {
	fd, err := os.Create(filename)
	if err != nil {
		return err
	}

	var pattern string

	for y := 0; y < height; y++ {
		line := ""
		for x := 0; x < width; x++ {
			char := "b"
			if grid[y][x] {
				char = "o"
			}

			line += char
		}

		// if first row is: 001011110, then line is now:
		//                  bboboooob

		encoded := RunLengthEncode(line)

		// and now its:     2bob4ob
		pattern += encoded

		if y != height-1 {
			pattern += "$"
		}
	}

	pattern += "!"

	wrapped := ""
	for idx, char := range pattern {
		if idx%70 == 0 && idx != 0 {
			wrapped += "\n"
		}
		wrapped += string(char)
	}

	_, err = fmt.Fprintf(fd, "#N %s\nx = %d, y = %d, rule = %s\n%s\n",
		filename, width, height, rule, wrapped)

	if err != nil {
		return err
	}

	return nil
}

// by peterSO on
// https://codereview.stackexchange.com/questions/238893/run-length-encoding-in-golang
func RunLengthEncode(s string) string {
	e := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		j := i + 1
		for ; j <= len(s); j++ {
			if j < len(s) && s[j] == c {
				continue
			}
			if j-i > 1 {
				e = strconv.AppendInt(e, int64(j-i), 10)
			}
			e = append(e, c)
			break
		}
		i = j - 1
	}
	return string(e)
}
