package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"runtime"
	"strings"
)

// reused by GoRuntinesStatus()
var buffer []byte
var output *bytes.Buffer

// This uses the most memory.  There has to be a better way.
const DEBUG_THIS bool = false

/*
var GStatus map[string]string = map[string]string{
	"idle":         "I",
	"runnable":     "r",
	"running":      "R",
	"syscall":      "s",
	"waiting":      "W",
	"dead":         "D",
	"copystack":    "C",
	"preempted":    "P",
	"sleep":        "S",
	"select":       "s", // missing
	"chan receive": "<", // missing
	"chan send":    ">", // missing
}
*/

// Map Status to Symbol.
func GStatusMapper(status []byte) byte {
	switch status[0] {
	case 'c':
		if status[1] == 'o' {
			return 'C'
		}
		switch status[5] {
		case 'r':
			return '<'
		case 's':
			return '>'
		}
		return '?'
	case 'd':
		return 'D'
	case 'i':
		return 'I'
	case 'p':
		return 'P'
	case 'r':
		switch status[4] {
		case 'a':
			return 'r'
		case 'i':
			return 'R'
		}
		return '?'
	case 's':
		switch status[1] {
		case 'y':
			return 's'
		case 'l':
			return 'S'
		case 'e':
			return 's'
		}
		return '?'
	case 'w':
		return 'W'
	default:
		return '?'
	}
}

// Get Go Routines and map the Status.  See GStatus.
func GoRoutinesStatus() []byte {
	if output == nil {
		log.Println("new bytes.Buffer{}")
		output = &bytes.Buffer{}
	}
	output.Reset()

	// Map status to letter.
	/*
		if GStatus == nil {
			GStatus = make(map[string]string)
			GStatus = map[string]string{
				"idle":         "I",
				"runnable":     "r",
				"running":      "R",
				"syscall":      "s",
				"waiting":      "W",
				"dead":         "D",
				"copystack":    "C",
				"preempted":    "P",
				"sleep":        "S",
				"select":       "s", // missing
				"chan receive": "<", // missing
				"chan send":    ">", // missing
			}
		}
	*/

	// 2K works, we don't have to grow any more.
	if buffer == nil {
		buffer = make([]byte, 2048)
	}
	read := runtime.Stack(buffer, true)

	if read == cap(buffer) {
		log.Printf("Increasing buffer from (%d bytes)\n", cap(buffer))
		buffer = make([]byte, cap(buffer)+1024)
		return GoRoutinesStatus()
	}

	var buff []byte = buffer[:read]

	// fmt.Println(string(buffer[0:read]))
	// This is horribly inefficient (memory allocation wise)
	var pos, lineend int
	var slice []byte
	var space, spaceend int
	var status []byte
	var count int

	for pos != -1 {
		if DEBUG_THIS {
			log.Printf("POS: %d (%d)\n", pos, len(buff))
		}
		lineend = bytes.Index(buff[pos:], []byte("\n"))
		if DEBUG_THIS {
			log.Printf("LineEnd: %d\n", lineend)
		}
		if lineend != -1 {
			lineend += pos
			slice = buff[pos:lineend]
			pos = lineend + 1
		} else {
			slice = buffer[pos:]
			pos = -1
		}

		// Process line
		if bytes.HasPrefix(slice, []byte("goroutine ")) {

			if DEBUG_THIS {
				log.Printf("GoRoutine %d [%s]\n", count, slice)
				count++
			}

			// Found a gorutine line
			space = bytes.Index(slice, []byte{'['})
			spaceend = bytes.Index(slice[space+1:], []byte{']'})
			if spaceend == -1 {
				status = slice[space+1:]
			} else {
				spaceend += space + 1
				// log.Printf("space %d, spaceend %d\n", space, spaceend)
				status = slice[space+1 : spaceend]
			}
			// Ok, status looks like what we're looking for here!

			if DEBUG_THIS {
				log.Printf("Status: [%s]\n", status)
			}

			space = bytes.Index(status, []byte{','})
			if space != -1 {
				status = status[:space]
				// log.Printf("Status now: [%s]\n", status)
			}
			rstatus := GStatusMapper(status)
			//rstatus, ok := GStatus[string(status)]
			output.WriteByte(rstatus)
			/*
				if ok {
					output.WriteString(rstatus)
				} else {
					log.Printf("** Status [%s] not found.\n", status)
				}
			*/
		}

	}

	if false {
		var reader = bytes.NewReader(buffer[0:read])
		var scanner = bufio.NewScanner(reader)
		for scanner.Scan() {
			var text = scanner.Text()
			if strings.HasPrefix(text, "goroutine ") {
				// fmt.Println(text)
				// goroutine 20 [runnable]:
				// goroutine 1 [select, 1 minutes]:
				// goroutine 17 [chan receive]:

				// Get ID and get Status.
				parts := strings.SplitN(text, " ", 3)
				/*
					gid, err := strconv.Atoi(parts[1])
					if err != nil {
						continue
					}
				*/
				status := parts[2][1 : len(parts[2])-2]
				if strings.Contains(status, ",") {
					status = strings.Split(status, ",")[0]
				}
				/*
					rstatus := GStatusMapper(status)
					output.WriteByte(rstatus)


					rstatus, ok := GStatus[status]
					if ok {
						output.WriteString(rstatus)
					} else {
						log.Printf("Status %s not found.\n[%s]\n", rstatus, text)
					}
				*/
			}
		}
	}

	return output.Bytes()
}

// Return a nicely formatted string representing memory usage.
func ReprMem(size uint64, output *bytes.Buffer) {
	if output == nil {
		return
	}
	output.Reset()
	var value float64 = float64(size)
	var units byte = ' '

	if value > 1024 {
		// In KB
		units = 'K'
		value /= 1024
	}
	if value > 1024 {
		// In MB
		units = 'M'
		value /= 1024
	}
	if value > 1024 {
		// Im GB
		units = 'G'
		value /= 1024
	}
	fmt.Fprintf(output, "%0.2f", value)
	if output.Len() > 4 {
		output.Reset()
		fmt.Fprintf(output, "%0.1f", value)
	}
	if output.Len() > 4 {
		output.Reset()
		fmt.Fprintf(output, "%0.0f", value)
	}
	if units != ' ' {
		output.WriteByte(units)
	}
}

// Return array of memory usage information
func Memory(result *map[string]*bytes.Buffer) {
	var memstats runtime.MemStats

	runtime.ReadMemStats(&memstats)

	if (*result)["Sys"] == nil {
		(*result)["Sys"] = &bytes.Buffer{}
	}

	ReprMem(memstats.Sys, (*result)["Sys"])
	// ReprMem(memstats.HeapAlloc, (*result)["Heap"])
	/*
		if (*result)["HeapSys"] == nil {
			(*result)["HeapSys"] = &bytes.Buffer{}
		}
	*/

	if (*result)["Heap"] == nil {
		(*result)["Heap"] = &bytes.Buffer{}
	}

	ReprMem(memstats.HeapAlloc, (*result)["Heap"])

	if (*result)["HeapSys"] == nil {
		(*result)["HeapSys"] = &bytes.Buffer{}
	}

	ReprMem(memstats.HeapSys, (*result)["MeapSys"])
	if (*result)["StackSys"] == nil {
		(*result)["StackSys"] = &bytes.Buffer{}
	}

	ReprMem(memstats.StackSys, (*result)["StackSys"])

	// Don't scale the Number of GCs, it is good as-is.
	if (*result)["NumGC"] == nil {
		(*result)["NumGC"] = &bytes.Buffer{}
	}
	(*result)["NumGC"].Reset()
	fmt.Fprintf((*result)["NumGC"], "%d", memstats.NumGC)
	// ReprMem(uint64(memstats.NumGC), (*result)["NumGC"])

	/*
		(*result)["HeapInUse"] = ReprMem(memstats.HeapInuse)
		(*result)["HeapSys"] = ReprMem(memstats.HeapSys)
		(*result)["Sys"] = ReprMem(memstats.Sys)
		(*result)["StackInUse"] = ReprMem(memstats.StackInuse)
		(*result)["StackSys"] = ReprMem(memstats.StackSys)
		(*result)["GCSys"] = ReprMem(memstats.GCSys)
	*/
}