package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"

	"github.com/iancoleman/orderedmap"
	"github.com/xuri/excelize/v2"
)

// Initial concept from https://stackoverflow.com/q/68621039

func main() {
	jsonFile := "test.json"
	parentNode := "input"
	sheetName := "Sheet2"
	outputFilename := "test.xlsx"

	var xlsx *excelize.File
	var s []byte

	var sheetIndex int
	var err error

	var cell string
	var row, column int

	// TODO - truncate sheetName to the maximum 31 characters

	// Check if xlsx file exists already, and if it does then open and append data
	if fileExists(outputFilename) {
		fmt.Printf("Output spreadsheet '%s' already exists.\n", outputFilename)
		xlsx, err = excelize.OpenFile(outputFilename)
		if err != nil {
			fmt.Println(err)
			return
		}

		// Since we have an existing workbook, check if the sheet we want to write to already exists
		sheetFound := false
		for index, name := range xlsx.GetSheetMap() {
			if name == sheetName {
				fmt.Printf("Found worksheet '%s' at index '%d'\n", sheetName, index)
				sheetFound = true
			}
		}
		if !sheetFound {
			// Create the sheet
			fmt.Printf("Creating worksheet '%s'\n", sheetName)
			sheetIndex, err = xlsx.NewSheet(sheetName)
			if err != nil {
				fmt.Printf("Error creating worksheet '%s' : %s\n", sheetName, err)
			}
			// Set active worksheet
			fmt.Printf("Setting active sheet to index %d", sheetIndex)
			xlsx.SetActiveSheet(sheetIndex)
		}

	} else {
		fmt.Printf("Creating output spreadsheet '%s'\n", outputFilename)
		// Create the file
		xlsx = excelize.NewFile()

		// Rename the default Sheet1 to this worksheet name
		if sheetName != "Sheet1" {
			fmt.Printf("Renaming default worksheet to '%s'\n", sheetName)
			err = xlsx.SetSheetName("Sheet1", sheetName)
			if err != nil {
				fmt.Printf("Error setting sheet name to '%s': %s\n", sheetName, err)
			}
		}
	}

	// Read the json input file
	if fileExists(jsonFile) {
		s, err = os.ReadFile(jsonFile)
		if err != nil {
			panic(err)
		}
	} else {
		fmt.Printf("Input JSON file '%s' does not exist.\n", jsonFile)
	}

	// Read the json into an orderedmap to preserve the ordering of json structure
	o := orderedmap.New()
	err = json.Unmarshal([]byte(s), &o)
	if err != nil {
		fmt.Printf("JSON Unmarshal error %s\n", err)
	}

	// Assume that our content is within the first top-level key
	topLevel := o.Keys()
	fmt.Printf("topLevel: %v\n", topLevel)
	parentNode = topLevel[0]

	// Get a reference to the top level node we specified earlier
	// TODO - validate this key exists
	vislice, ok := o.Get(parentNode)
	if !ok {
		fmt.Printf("Missing key for multitype array")
	}
	vslice := vislice.([]interface{})

	// Get the keys for the first element so we know what the column names will be
	columnMap := vslice[0].(orderedmap.OrderedMap)
	columnNames := columnMap.Keys()
	fmt.Printf("Creating excel workbook with following headings : '%v'\n", columnNames)

	// Add the header row
	row = 1
	column = 1
	for i := 0; i < len(columnNames); i++ {
		cell, _ = excelize.CoordinatesToCellName(column, row)
		fmt.Printf("Setting cell %s to value %s\n", cell, columnNames[i])
		xlsx.SetCellValue(sheetName, cell, columnNames[i])
		column++
	}

	// Set starting row for data
	row = 2

	fmt.Printf("Adding %d rows of data to spreadsheet.\n", len(vslice))

	// Iterate the whole slice to get the data and add to the worksheet
	for i, v := range vslice {
		// Print the contents each slice
		//fmt.Printf("'%d' : '%v'\n", i, v)

		//fmt.Printf("Slice element %d\n", i)
		_ = i

		// Each iteration should start back at column 1
		column = 1

		// Print each key-value pair contained in this slice
		vmap := v.(orderedmap.OrderedMap)
		k := vmap.Keys()

		for j := range k {
			//a = string(asciiValue)
			//cell = a + strconv.Itoa(2+i)
			cell, _ = excelize.CoordinatesToCellName(column, row)

			e, _ := vmap.Get(k[j])
			//fmt.Printf("Setting cell %s to value %v\n", cell, e)
			xlsx.SetCellValue(sheetName, cell, e)

			// Move to the next column
			//asciiValue++
			column++
		}
		//fmt.Printf("k: %v\n", k)

		// Move to next row
		row++
	}

	// Close off the file
	if err := xlsx.SaveAs(outputFilename); err != nil {
		log.Fatal(err)
		return
	}

	fmt.Printf("Excel file '%s' generated sucessfully.\n", outputFilename)

	/*
		//Test https://gitlab.com/c0b/go-ordered-json
		var om *ordered.OrderedMap = ordered.NewOrderedMap()
		err = json.Unmarshal([]byte(file), om)
		if err != nil {
			fmt.Printf("error: '%s'\n", err)
		}

		iter := om.EntriesIter()
		for {
			pair, ok := iter()
			if !ok {
				break
			}
			fmt.Printf("%-12s: %v\n", pair.Key, pair.Value)
		}
	*/
}

func fileExists(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return !info.IsDir()
}

func TestUnmarshalJSON() {
	s := `{
  "number": 4,
  "string": "x",
  "z": 1,
  "a": "should not break with unclosed { character in value",
  "b": 3,
  "slice": [
    "1",
    1
  ],
  "orderedmap": {
    "e": 1,
    "a { nested key with brace": "with a }}}} }} {{{ brace value",
	"after": {
		"link": "test {{{ with even deeper nested braces }"
	}
  },
  "test\"ing": 9,
  "after": 1,
  "multitype_array": [
    "test",
	1,
	{ "map": "obj", "it" : 5, ":colon in key": "colon: in value" },
	[{"inner": "map"}]
  ],
  "should not break with { character in key": 1
}`
	o := orderedmap.New()
	err := json.Unmarshal([]byte(s), &o)
	if err != nil {
		fmt.Printf("JSON Unmarshal error", err)
	}
	// Check the root keys
	expectedKeys := []string{
		"number",
		"string",
		"z",
		"a",
		"b",
		"slice",
		"orderedmap",
		"test\"ing",
		"after",
		"multitype_array",
		"should not break with { character in key",
	}
	k := o.Keys()
	for i := range k {
		if k[i] != expectedKeys[i] {
			fmt.Printf("Unmarshal root key order", i, k[i], "!=", expectedKeys[i])
		}
	}
	// Check nested maps are converted to orderedmaps
	// nested 1 level deep
	expectedKeys = []string{
		"e",
		"a { nested key with brace",
		"after",
	}
	vi, ok := o.Get("orderedmap")
	if !ok {
		fmt.Printf("Missing key for nested map 1 deep")
	}
	v := vi.(orderedmap.OrderedMap)
	k = v.Keys()
	for i := range k {
		if k[i] != expectedKeys[i] {
			fmt.Printf("Key order for nested map 1 deep ", i, k[i], "!=", expectedKeys[i])
		}
	}
	// nested 2 levels deep
	expectedKeys = []string{
		"link",
	}
	vi, ok = v.Get("after")
	if !ok {
		fmt.Printf("Missing key for nested map 2 deep")
	}
	v = vi.(orderedmap.OrderedMap)
	k = v.Keys()
	for i := range k {
		if k[i] != expectedKeys[i] {
			fmt.Printf("Key order for nested map 2 deep", i, k[i], "!=", expectedKeys[i])
		}
	}
	// multitype array
	expectedKeys = []string{
		"map",
		"it",
		":colon in key",
	}
	vislice, ok := o.Get("multitype_array")
	if !ok {
		fmt.Printf("Missing key for multitype array")
	}
	vslice := vislice.([]interface{})
	vmap := vslice[2].(orderedmap.OrderedMap)
	k = vmap.Keys()
	for i := range k {
		if k[i] != expectedKeys[i] {
			fmt.Printf("Key order for nested map 2 deep", i, k[i], "!=", expectedKeys[i])
		}
	}
	// nested map 3 deep
	vislice, _ = o.Get("multitype_array")
	vslice = vislice.([]interface{})
	expectedKeys = []string{"inner"}
	vinnerslice := vslice[3].([]interface{})
	vinnermap := vinnerslice[0].(orderedmap.OrderedMap)
	k = vinnermap.Keys()
	for i := range k {
		if k[i] != expectedKeys[i] {
			fmt.Printf("Key order for nested map 3 deep", i, k[i], "!=", expectedKeys[i])
		}
	}
}
