package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
)

var re *regexp.Regexp

func main() {
	var input []byte
	var noDecompress bool
	var printSize bool
	if len(os.Args) > 1 {
		for i := 1; i < len(os.Args); i++ {
			if os.Args[i][0] != '-' {
				// Assume it's a filename
				// Read from the given filename
				input = fileToByteSlice(os.Args[1])
			} else {
				switch os.Args[i] {
				case "-size":
					printSize = true
				case "-dry":
					// -dry implies printSize
					printSize = true
					noDecompress = true
				}
			}
		}
	}
	if len(input) == 0 {
		// Must be stdin
		input = stdinToByteSlice()
	}
	input = bytes.TrimSpace(input)
	// re matches compression markers and submatches the values we care about
	re = regexp.MustCompile(`\((\d*)x(\d*)\)`)

	if noDecompress {
		cnt := depthDecompressLength(input)
		fmt.Println("Total Decompressed Bytes:", cnt)
		os.Exit(0)
	}
	output := depthDecompress(input)
	fmt.Println(string(output))
	if printSize {
		fmt.Println("Total Decompressed Bytes:", len(output))
	}
}

// depthDecompressLength counts how many uncompressed bytes are
// in the byte slice by unravelling the compression levels
// recursively
func depthDecompressLength(cmp []byte) uint64 {
	mrkParts := re.FindStringSubmatch(string(cmp))
	if len(mrkParts) < 3 {
		// No compressionmarker, just return cmp length
		return uint64(len(cmp))
	}
	marker, mrkBytes, mrkDupe := []byte(mrkParts[0]), atoi(mrkParts[1]), atoi(mrkParts[2])
	mrkPos := bytes.Index(cmp, marker)
	if mrkPos > 0 {
		cmp = cmp[mrkPos:]
	}
	recurBytes := bytes.TrimPrefix(cmp, marker)[:mrkBytes]
	remainder := bytes.TrimPrefix(cmp, append(marker, recurBytes...))
	return uint64(mrkPos) + (depthDecompressLength(recurBytes) * uint64(mrkDupe)) + depthDecompressLength(remainder)
}

func depthDecompress(cmp []byte) []byte {
	mrkParts := re.FindStringSubmatch(string(cmp))
	if len(mrkParts) < 3 {
		// No compression marker, just return cmp length
		return cmp
	}
	marker, mrkBytes, mrkDupe := []byte(mrkParts[0]), atoi(mrkParts[1]), atoi(mrkParts[2])
	mrkPos := bytes.Index(cmp, marker)
	var ret []byte
	if mrkPos > 0 {
		ret = cmp[:mrkPos]
		cmp = cmp[mrkPos:]
	}
	recurBytes := bytes.TrimPrefix(cmp, marker)[:mrkBytes]
	remainder := bytes.TrimPrefix(cmp, append(marker, recurBytes...))

	ret = append(ret, bytes.Repeat(depthDecompress(recurBytes), mrkDupe)...)
	return append(ret, depthDecompress(remainder)...)
}

func fileToByteSlice(fn string) []byte {
	var c []byte
	var err error
	c, err = ioutil.ReadFile(fn)
	if err != nil {
		fmt.Println("Unable to read file: " + fn)
		os.Exit(1)
	}
	return c
}

func stdinToByteSlice() []byte {
	var input string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input += scanner.Text()
	}
	return []byte(input)
}

func atoi(i string) int {
	var ret int
	var err error
	if ret, err = strconv.Atoi(i); err != nil {
		log.Fatal("Invalid Atoi")
	}
	return ret
}
