package main

import (
	"encoding/json"
	"flag"
	"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"
	//worksheetName := "Sheet2"
	//outputFilename := "test.xlsx"

	// Command line arguments
	var inputJson string
	var worksheetName string
	var outputFilename string
	var boldTopRow bool
	var freezeTopRow bool
	var autoFilter bool

	// Process command line arguments
	flag.StringVar(&inputJson, "inputJson", "./input.json", "Full path to input json data file")
	flag.StringVar(&outputFilename, "outputFilename", "./output.xlsx", "Filename for excel worksheet output")
	flag.StringVar(&worksheetName, "worksheetName", "Sheet1", "Label to set on worksheet")
	flag.BoolVar(&boldTopRow, "bold-toprow", true, "Sets the top row of the worksheet to bold")
	flag.BoolVar(&freezeTopRow, "freeze-toprow", true, "Freezes the first row of the Excel worksheet")
	flag.BoolVar(&autoFilter, "autofilter", true, "Sets the auto filter on the first row")
	flag.Parse()

	var xlsx *excelize.File
	var s []byte

	var sheetIndex int
	var err error

	var cell string
	var row, column int

	// TODO - truncate worksheetName 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)
			os.Exit(1)
		}

		// 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 == worksheetName {
				fmt.Printf("Found worksheet '%s' at index '%d'\n", worksheetName, index)
				sheetFound = true
			}
		}
		if !sheetFound {
			// Create the sheet
			fmt.Printf("Creating worksheet '%s'\n", worksheetName)
			sheetIndex, err = xlsx.NewSheet(worksheetName)
			if err != nil {
				fmt.Printf("Error creating worksheet '%s' : %s\n", worksheetName, 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 worksheetName != "Sheet1" {
			fmt.Printf("Renaming default worksheet to '%s'\n", worksheetName)
			err = xlsx.SetSheetName("Sheet1", worksheetName)
			if err != nil {
				fmt.Printf("Error setting sheet name to '%s': %s\n", worksheetName, err)
			}
		}
	}

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

	// Unmarshal 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("Detected toplevel json key as: '%s'\n", topLevel[0])
	parentNode = topLevel[0]

	// Get a reference to the top level node we specified earlier
	vislice, ok := o.Get(parentNode)
	if !ok {
		fmt.Printf("Missing key for multitype array")
	}
	// Get an interface that we can work with to access the sub elements
	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)

	// Set the style for the header values
	// Just handling bold for now but we can do other styles too as per https://xuri.me/excelize/en/style.html#NewStyle
	headerStyle, err2 := xlsx.NewStyle(&excelize.Style{
		Font: &excelize.Font{
			Bold: boldTopRow,
		},
	})
	if err2 != nil {
		fmt.Printf("Error generating header style : '%s'\n", err2)
	}

	row = 1
	column = 1

	// Set the style
	err = xlsx.SetRowStyle(worksheetName, row, row, headerStyle)
	if err != nil {
		fmt.Printf("Error setting header style : '%s'\n", err)
	}

	// Add the header row
	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(worksheetName, cell, columnNames[i])
		//xlsx.SetCellStyle(worksheetName, cell, cell, headerStyle)
		column++
	}

	// Freeze top row if requested, see https://xuri.me/excelize/en/utils.html#SetPanes
	if freezeTopRow {
		err = xlsx.SetPanes(worksheetName, &excelize.Panes{
			Freeze:      true,
			Split:       false,
			XSplit:      0,
			YSplit:      1,
			TopLeftCell: "A2",
			ActivePane:  "bottomLeft",
			Panes: []excelize.PaneOptions{
				{SQRef: "A2", ActiveCell: "A2", Pane: "bottomLeft"},
			},
		})
		if err != nil {
			fmt.Printf("Error freezing top row : '%s'\n", err)
		}
	}

	// Handle autofilter
	if autoFilter {
		// cell is still a reference to the last cell in the header row
		filterRange := "A1:" + cell
		fmt.Printf("Setting autofilter to range '%s'\n", filterRange)
		err = xlsx.AutoFilter(worksheetName, filterRange, nil)
		if err != nil {
			fmt.Printf("Error setting autofilter : '%s'\n", err)
		}
	}

	// Now process the remaining data in our json input
	// Set starting row for data
	row = 2

	// Iterate the whole slice to get the data and add to the worksheet
	fmt.Printf("Adding %d rows of data to spreadsheet.\n", len(vslice))
	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(worksheetName, 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])
		}
	}
}
