package mml

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"ems.agt/lib/dborm"
	"ems.agt/lib/global"
	"ems.agt/lib/log"
	"github.com/go-resty/resty/v2"
	"golang.org/x/crypto/ssh"
)

type Param struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type MmlCommand struct {
	Operation string                 `json:"operation"`
	Object    string                 `json:"object"`
	Params    []Param                `json:"params"`
	PaList    []string               `json:"paList"`
	AaList    []string               `json:"aaList"`
	AaMap     map[string]interface{} `json:"aaMap"`
	NaMap     map[string]interface{} `json:"naMap"`
	AaUri     []string               `json:"aaUri"`
	AaLoc     []string               `json:"aaLoc"` // for loc parameter
}

type MmlVar struct {
	Version string `json:"version"`
	Output  string `json:"output"`
	Limit   int    `json:"limit"`
	User    string `json:"user"`
	Token   string `josn:"token"`
}

var OmcMmlVar *MmlVar

func init() {
	OmcMmlVar = &MmlVar{
		Version: "16.1.1",
		Output:  DefaultFormatType,
		Limit:   50,
	}
}

func SetOmcMmlVarOutput(output string) {
	OmcMmlVar.Output = output
}

func SetOmcMmlVarLimit(limit int) {
	OmcMmlVar.Limit = limit
}

func splitByColon(str string) []string {
	return splitBy(str, ':')
}

func splitByComma(str string) []string {
	return splitBy(str, ',')
}

func splitBy(str string, sep rune) []string {
	var result []string
	var stack []string
	var current []rune
	var quotes, apoFlag bool = false, false

	for _, c := range str {
		if c == '{' || c == '[' || (c == '\'' && apoFlag == false) || (c == '"' && quotes == false) { // "'"
			apoFlag = true
			quotes = true
			stack = append(stack, string(c))
		} else if c == '}' || c == ']' || (c == '\'' && apoFlag == true) || (c == '"' && quotes == true) {
			apoFlag = false
			quotes = false
			if len(stack) > 0 {
				stack = stack[:len(stack)-1]
			}
		}

		if c == sep && len(stack) == 0 {
			result = append(result, string(current))
			current = []rune{}
		} else {
			current = append(current, c)
		}
	}

	result = append(result, string(current))
	return result
}

func ParseMMLCommand(mmlStr string, mmlComms *[]MmlCommand) error {
	log.Info("ParseMMLCommand processing ...")
	log.Debug("mmlStr: ", mmlStr)

	mc := new(MmlCommand)
	reg := regexp.MustCompile(`\s*;\s*`)
	mmls := reg.Split(mmlStr, -1)
	for _, mml := range mmls {
		log.Trace("mml:", mml)
		if len(mml) == 0 {
			continue
		}
		//reg := regexp.MustCompile(`\s*:\s*`)
		//ms := reg.Split(mml, -1)
		ms := splitByColon(mml)
		if len(ms) < 1 || len(ms) > 2 {
			err := global.ErrMmlInvalidCommandFormat
			log.Error(err)
			return err
		}
		if len(ms) == 2 {
			cmd := strings.Trim(ms[0], " ")
			reg = regexp.MustCompile(`\s+`)
			cs := reg.Split(cmd, -1)
			//cs := strings.Split(cmd, " ")
			if len(cs) == 2 {
				mc.Operation = cs[0]
				mc.Object = cs[1]
			} else {
				err := global.ErrMmlInvalidCommandFormat
				log.Error(err)
				return err
			}
			//reg = regexp.MustCompile(`\s*,\s*`)
			//reg = regexp.MustCompile(`(?U)(?<!\{[^{}]*),(?![^{}]*\})|(?<!\"[^\"]*),(?![^\"]*\")|(?<!\[[^\[\]]*),(?![^\[\]]*\])`)
			//reg = regexp.MustCompile(`,[^'"\(\)]+`)
			//params := reg.Split(ms[1], -1)
			params := splitByComma(strings.Trim(ms[1], " "))
			//params := strings.Split(ms[1], ",")
			for _, p := range params {
				log.Trace("p:", p)
				if p == "" {
					continue
				}
				mc.PaList = append(mc.PaList, p)
				reg = regexp.MustCompile(`\s*=\s*`)
				pvs := reg.Split(p, -1)
				log.Trace("pvs:", pvs)
				if len(pvs) == 2 {
					mc.Params = append(mc.Params, Param{Name: pvs[0], Value: pvs[1]})
				} else {
					err := global.ErrMmlInvalidCommandFormat
					log.Error(err)
					return err
				}
			}
		} else if len(ms) == 1 {
			cmd := ms[0]
			reg = regexp.MustCompile(`\s+`)
			cs := reg.Split(cmd, -1)
			//cs := strings.Split(cmd, " ")
			if len(cs) == 2 {
				mc.Operation = cs[0]
				mc.Object = cs[1]
			} else {
				err := global.ErrMmlInvalidCommandFormat
				log.Error(err)
				return err
			}
		} else {
			err := global.ErrMmlInvalidCommandFormat
			log.Error(err)
			return err
		}
		err := ParseMMLAlias(mc)
		if err != nil {
			err := global.ErrMmlInvalidCommandFormat
			log.Error(err)
			return err
		}
		*mmlComms = append(*mmlComms, *mc)
	}
	return nil
}

func ParseMMLAlias(mml *MmlCommand) error {
	where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
	mc, err := dborm.XormGetMmlCommand("mml_command", where)
	if err != nil {
		log.Error("Failed to XormGetMmlCommand: ", err)
		return err
	}
	if mc == nil {
		err := errors.New("Not found mml map")
		log.Error(err)
		return err
	}
	log.Debug("mml command: ", mc)
	aaMap := make(map[string]interface{})
	naMap := make(map[string]interface{})
	for _, pn := range mml.Params {
		log.Trace("pn: ", pn)
		for _, param := range mc.ParamJson {
			log.Trace("param: ", param)
			var pv string
			if pn.Name == param.Name {
				if param.Apostr == "true" {
					pv = fmt.Sprintf("'%v'", pn.Value)
				} else {
					pv = fmt.Sprintf("%v", pn.Value)
				}
				var aa, av string

				if param.Alias != "" {
					aa = fmt.Sprintf("%s=%v", param.Alias, pv)
					av = fmt.Sprintf("%v", pv)
					switch param.Type {
					case "int":
						aaMap[param.Alias] = pn.Value
						naMap[param.Alias] = pn.Value
					default:
						aaMap[param.Alias] = pv
						naMap[param.Alias] = fmt.Sprintf("%v", pn.Value)
					}
				} else {
					aa = fmt.Sprintf("%s=%v", param.Name, pv)
					av = fmt.Sprintf("%v", pv)
					switch param.Type {
					case "int":
						aaMap[param.Name] = pn.Value
						naMap[param.Name] = pn.Value
					default:
						aaMap[param.Name] = pv
						naMap[param.Name] = fmt.Sprintf("%v", pn.Value)
					}
				}
				if param.Loc == "" || param.Loc == "true" {
					mml.AaLoc = append(mml.AaLoc, aa)
					mml.AaUri = append(mml.AaUri, av)
				}
				//mml.AaMap = append(mml.AaMap, aaMap)
				mml.AaList = append(mml.AaList, aa)
				break
			}
		}
	}
	mml.AaMap = aaMap
	mml.NaMap = naMap
	log.Trace("mml.AaMap: ", mml.AaMap)
	log.Trace("mml.NaMap: ", mml.NaMap)
	log.Trace("mml.AaList: ", mml.AaList)

	return nil
}

func ParseMMLParams(mmlComms *[]MmlCommand) error {
	for _, mml := range *mmlComms {
		where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
		mc, err := dborm.XormGetMmlCommand("mml_command", where)
		if err != nil {
			log.Error("Failed to XormGetMmlCommand: ", err)
			return err
		}
		if mc == nil {
			err := errors.New("Not found mml map")
			log.Error(err)
			return err
		}
		log.Debug("mml command: ", mc)
		for _, pn := range mml.Params {
			log.Trace("pn: ", pn)
			for _, param := range mc.ParamJson {
				log.Trace("param: ", param)
				var pv string
				if pn.Name == param.Name {
					if param.Apostr == "true" {
						pv = fmt.Sprintf("'%v'", pn.Value)
					} else {
						pv = fmt.Sprintf("%v", pn.Value)
					}
					var aa string
					aaMap := make(map[string]interface{})
					if param.Alias != "" {
						aa = fmt.Sprintf("%s=%v", param.Alias, pv)
						switch param.Type {
						case "int":
							aaMap[param.Alias] = pn.Value
						case "string":
							aaMap[param.Alias] = pv
						}
					} else {
						aa = fmt.Sprintf("%s=%v", param.Name, pv)
						switch param.Type {
						case "int":
							aaMap[param.Name] = pn.Value
						case "string":
							aaMap[param.Name] = pv
						}
					}
					//mml.AaMap = append(mml.AaMap, aaMap)
					mml.AaList = append(mml.AaList, aa)
					break
				}
			}
		}
		log.Trace("mml.AaMap: ", mml.AaMap)
		log.Trace("mml.AaList: ", mml.AaList)
		*mmlComms = append(*mmlComms, mml)
	}

	return nil
}

func parseRequestUri(httpUri string, mmlMap *dborm.MmlHttpMap, mml *MmlCommand) string {
	requestURI := fmt.Sprintf("%s%s", httpUri, mmlMap.URI)
	if mmlMap.ExtUri != "" && len(mml.AaUri) > 0 {
		extUri := strings.Join(mml.AaUri, "/")
		requestURI = requestURI + fmt.Sprintf(mmlMap.ExtUri, extUri)
	}
	if mmlMap.Params != "" {
		params := strings.Join(mml.AaLoc, "+and+")
		params = strings.ReplaceAll(params, " ", "+") // replace " " to "+"
		log.Trace("params:", params)
		if mmlMap.ParamTag == "SQL" && strings.TrimSpace(params) != "" {
			params = "+where+" + params
		}
		requestURI = fmt.Sprintf("%s%s%s", requestURI, mmlMap.Params, params)
	}
	return requestURI
}

func TransMml2HttpReq(sshConn *ssh.ServerConn, httpUri, uerAgent string, mml *MmlCommand) (*[]byte, error) {
	log.Info("TransMml2HttpReq processing ...")
	log.Debug("mml: ", mml)

	where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
	mmlMap, err := dborm.XormGetMmlHttpMap("mml_http_map", where)
	if err != nil {
		log.Error("Failed to XormGetMmlHttpMap: ", err)
		return ParseErrorOutput(err), err
	}
	if mmlMap == nil {
		err := errors.New("Not found mml map")
		log.Error(err)
		return ParseErrorOutput(err), err
	}
	log.Trace("mmlMap: ", mmlMap)
	if mmlMap.Output == "" {
		mmlMap.Output = "{}"
	}
	outputJson := new(dborm.MmlOutput)
	err = json.Unmarshal([]byte(mmlMap.Output), outputJson)
	if err != nil {
		log.Error("Failed to Unmarshal:", err)
		return ParseErrorOutput(err), err
	}
	log.Trace("outputJson: ", outputJson)
	inputJson := new(dborm.MmlInput)
	log.Trace("mmlMap.Input: ", mmlMap.Input)
	if mmlMap.Input == "" {
		mmlMap.Input = "{}"
	}
	err = json.Unmarshal([]byte(mmlMap.Input), inputJson)
	if err != nil {
		log.Error("Failed to Unmarshal:", err)
		return ParseErrorOutput(err), err
	}
	log.Trace("inputJson: ", inputJson)

	var requestURI string
	var output *[]byte
	client := resty.New()
	switch strings.ToLower(mmlMap.Method) {
	case "get":
		requestURI = parseRequestUri(httpUri, mmlMap, mml)
		log.Debugf("method: Get requestURI: %s", requestURI)
		response, err := client.R().
			EnableTrace().
			SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
			SetHeaders(map[string]string{"User-Agent": uerAgent}).
			SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
			Get(requestURI)
		if err != nil {
			log.Error("Failed to Get:", err)
			output = ParseErrorOutput(err)
		} else {
			output = ParseOutputResponse(outputJson, response)
		}
	case "post":
		requestURI = parseRequestUri(httpUri, mmlMap, mml)
		body := ParseInputBody(inputJson, mml)
		log.Debugf("method: Post requestURI: %s", requestURI)
		response, err := client.R().
			EnableTrace().
			SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
			SetHeaders(map[string]string{"User-Agent": uerAgent}).
			SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
			SetBody(*body).
			Post(requestURI)
		if err != nil {
			log.Error("Failed to Post:", err)
			output = ParseErrorOutput(err)
		} else {
			output = ParseOutputResponse(outputJson, response)
		}
	case "put":
		requestURI = parseRequestUri(httpUri, mmlMap, mml)
		body := ParseInputBody(inputJson, mml)
		log.Debugf("method: Put requestURI: %s", requestURI)
		response, err := client.R().
			EnableTrace().
			SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
			SetHeaders(map[string]string{"User-Agent": uerAgent}).
			SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
			SetBody(*body).
			Put(requestURI)
		if err != nil {
			log.Error("Failed to Put:", err)
			output = ParseErrorOutput(err)
		} else {
			output = ParseOutputResponse(outputJson, response)
		}
	case "delete":
		requestURI = parseRequestUri(httpUri, mmlMap, mml)
		log.Debugf("method: Delete requestURI: %s", requestURI)
		response, err := client.R().
			EnableTrace().
			SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
			SetHeaders(map[string]string{"User-Agent": uerAgent}).
			SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
			Delete(requestURI)
		if err != nil {
			log.Error("Failed to Delete:", err)
			output = ParseErrorOutput(err)
		} else {
			output = ParseOutputResponse(outputJson, response)
		}
	case "patch":
		requestURI = parseRequestUri(httpUri, mmlMap, mml)
		log.Debugf("method: patch requestURI: %s", requestURI)
		response, err := client.R().
			EnableTrace().
			SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
			SetHeaders(map[string]string{"User-Agent": uerAgent}).
			SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
			Patch(requestURI)
		if err != nil {
			log.Error("Failed to Patch:", err)
			output = ParseErrorOutput(err)
		} else {
			output = ParseOutputResponse(outputJson, response)
		}
	default:
		err := errors.New("not found mml command")
		log.Error(err)
		output = ParseErrorOutput(err)
	}
	return output, nil
}

const (
	MaxMmlOutputBufferSize = 1000000
	FormatTypeJson         = "json"
	FormatTypeTable        = "table"
	DefaultFormatType      = FormatTypeTable
)

const (
	RetCodeSucceeded = 0
	RetCodeFailed    = 0
)

func ParseInputBody(inputJson *dborm.MmlInput, mml *MmlCommand) *[]byte {
	inputBody := make(map[string]interface{})
	log.Trace("mml.NaMap:", mml.NaMap)
	log.Trace("mml.AaMap:", mml.AaMap)
	if strings.ToLower(inputJson.BodyFmt) == "putdb" {
		for _, icol := range inputJson.Cols {
			log.Trace("icol:", icol)
			mml.NaMap[icol.Name] = icol.Value
		}
		inputBody[inputJson.BodyKey] = mml.NaMap
	} else {
		inputParams := make([]map[string]interface{}, 0)
		inputParams = append(inputParams, mml.NaMap)
		inputBody[inputJson.BodyKey] = inputParams
	}

	body, err := json.Marshal(inputBody)
	if err != nil {
		log.Error("Failed to marshal:", err)
	}

	log.Trace("inputBody:", inputBody)
	log.Trace("body:", string(body))
	return &body
}

func ParseOutputResponse(outputJson *dborm.MmlOutput, response *resty.Response) *[]byte {
	var output []byte
	var str bytes.Buffer

	switch response.StatusCode() {
	case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
		if OmcMmlVar.Output == FormatTypeJson {
			code := fmt.Sprintf("StatusCode = %d  status %s\n\n", response.StatusCode(), response.Status())
			title := formatTitle(outputJson.Title)

			json.Indent(&str, response.Body(), "", "    ")
			log.Trace(str.String())

			output = global.BytesCombine1([]byte(code), []byte(title), str.Bytes(), []byte("\n"))
		} else {
			log.Trace("Body:", string(response.Body()))
			mapDatas := make(map[string]interface{}, 0)

			err := json.Unmarshal(response.Body(), &mapDatas)
			if err != nil {
				log.Error("Failed to json.Unmarshal:", err)
				//output = *ParseErrorOutput(err)
				output = *ParseErrorOutput(string(response.Body()))
				return &output
			}
			log.Trace("mapDatas:", mapDatas)
			switch strings.ToLower(outputJson.RetFmt) {
			case "getdb":
				if len(mapDatas) > 0 {
					var data interface{}
					for _, data = range mapDatas {
						log.Trace("data:", data)
						break
					}
					if len(data.([]interface{})) > 0 {
						table := (data.([]interface{}))[0]
						log.Trace("table:", table)

						code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
						title := formatTitle(outputJson.Title)
						fmtResults := ParseTableOutput(outputJson, table)
						output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults))
					}
				}
			case "deletedb":
				var data interface{}
				for _, data = range mapDatas {
					log.Trace("data:", data)
					break
				}

				if len(data.(map[string]interface{})) > 0 {
					table := data.(map[string]interface{})
					code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
					fmtResults := ParseDBOperOutput(outputJson, table)
					output = global.BytesCombine1([]byte(code), []byte(fmtResults))
				}
			case "postdb":
				var data interface{}
				for _, data = range mapDatas {
					log.Trace("data:", data)
					break
				}

				if len(data.(map[string]interface{})) > 0 {
					table := data.(map[string]interface{})
					code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
					fmtResults := ParseDBOperOutput(outputJson, table)
					output = global.BytesCombine1([]byte(code), []byte(fmtResults))
				}
			case "putdb":
				var data interface{}
				for _, data = range mapDatas {
					log.Trace("data:", data)
					break
				}
				if len(data.(map[string]interface{})) > 0 {
					table := data.(map[string]interface{})
					code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
					fmtResults := ParseDBOperOutput(outputJson, table)
					output = global.BytesCombine1([]byte(code), []byte(fmtResults))
				}
			case "getnf":
				if len(mapDatas) > 0 {
					var data interface{}
					for _, data = range mapDatas {
						log.Trace("data:", data)
						break
					}
					if len(data.([]interface{})) > 0 {
						//table := (data.([]interface{}))[0]
						//log.Trace("table:", table)

						code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
						title := formatTitle(outputJson.Title)
						fmtResults := ParseNFTableOutput(outputJson, data)
						output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults))
					}
				}
			default:
				code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
				output = global.BytesCombine1([]byte(code))
			}

		}
	default:
		if OmcMmlVar.Output == FormatTypeJson {
			code := fmt.Sprintf("StatusCode = %d  status %s\n\n", response.StatusCode(), response.Status())
			//title := formatTitle("Network Element Information")

			json.Indent(&str, response.Body(), "", "    ")
			log.Trace(str.String())

			output = global.BytesCombine1([]byte(code), str.Bytes(), []byte("\n"))
		} else {
			log.Trace("Body:", string(response.Body()))
			mapResults := make(map[string]interface{}, 0)

			err := json.Unmarshal(response.Body(), &mapResults)
			if err != nil {
				log.Error("Failed to json.Unmarshal:", err)
				output = *ParseErrorOutput(string(response.Body()))
			} else {
				log.Trace("mapResults:", mapResults)
				errResult := mapResults["error"]
				log.Trace("errResult:", errResult)
				if len(errResult.(map[string]interface{})) > 0 {
					errCode, _ := strconv.Atoi(fmt.Sprintf("%v", errResult.(map[string]interface{})["errorCode"]))
					errorInfo := errResult.(map[string]interface{})["errorInfo"]
					output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo))
				}
			}
		}
	}
	return &output
}

func ParseDBOperOutput(outputJson *dborm.MmlOutput, cols any) string {
	var colOutput []dborm.ColOutput = outputJson.Cols
	var value, retFmtCols string
	if len(cols.(map[string]interface{})) > 0 {
		if len(colOutput) > 0 {
			coln := colOutput[0].Name
			value = fmt.Sprintf("%v", cols.(map[string]interface{})[coln])
			log.Tracef("coln:%s value:%s", coln, value)
			retFmtCols = colOutput[0].Display + " = " + value + "\n\n"
		}
	}

	return retFmtCols
}

func ParseNFTableOutput(outputJson *dborm.MmlOutput, cols any) string {
	var colOutput []dborm.ColOutput
	var fmtColName string
	var colName []string
	var spaceNum int = 1
	var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left"

	if outputJson.SepSpaceNum != 0 {
		spaceNum = outputJson.SepSpaceNum
	}
	if outputJson.AlignmentM != "" {
		alignmentM = outputJson.AlignmentM
	}
	if outputJson.AlignmentSN != "" {
		alignmentSN = outputJson.AlignmentSN
	}
	if outputJson.AlignmentSV != "" {
		alignmentSV = outputJson.AlignmentSV
	}

	maxLength := math.MinInt64
	for _, coln := range outputJson.Cols {
		log.Trace("coln:", coln)

		if len(coln.Display) > maxLength {
			maxLength = len(coln.Display)
		}
		if coln.Length < len(coln.Display) {
			coln.Length = len(coln.Display)
		}

		colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display))
		colOutput = append(colOutput, coln)
	}
	fmtColName = formatLineBySpace(&colName, spaceNum)
	log.Trace("fmtColName:", fmtColName)

	var retFmtCols string
	var fmtColValues []string
	var numberResult int
	// for _, colnvs := range cols.([]interface{}) {
	// 	log.Trace("colnvs:", colnvs)
	// 	if colnvs == nil {
	// 		break
	// 	}
	numberResult = len(cols.([]interface{}))

	if numberResult == 1 && outputJson.SingleList == true {
		colnv := cols.([]interface{})[0]
		log.Trace("colnv:", colnv)

		var fmtNV []string
		for _, coln := range colOutput {
			fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display)
			log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName)
			value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
			fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value)
			fmtNV = append(fmtNV, fmtName+":  "+fmtValue)
		}

		fmtResults := strings.Join(fmtNV, "\n")
		log.Tracef("fmtResults:\n%s", fmtResults)
		fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
		retFmtCols = fmtResults + "\n\n" + fmtEnd
		log.Tracef("retFmtCols:\n%s", retFmtCols)
		return retFmtCols
	} else {
		for i := 0; i < numberResult; i++ {
			colnv := cols.([]interface{})[i]
			log.Trace("colnv:", colnv)
			var colValues []string
			var newVal []string
			for _, coln := range colOutput {
				value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
				if len(coln.Alias) != 0 {
					enumVal, _ := strconv.Atoi(value)
					value = parseEnumAlias(&(coln.Alias), enumVal)
				}
				newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value))
			}
			colValues = append(colValues, formatLineBySpace(&newVal, spaceNum))
			log.Trace("colValues:", colValues)
			fmtColValues = append(fmtColValues, strings.Join(colValues, "\n"))
			log.Trace("fmtColValues:", fmtColValues)
		}
		fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
		retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
		log.Tracef("retFmtCols:\n%s", retFmtCols)
		return retFmtCols
	}
}

func ParseTableOutput(outputJson *dborm.MmlOutput, cols any) string {
	var colOutput []dborm.ColOutput
	var fmtColName string
	var colName []string
	var spaceNum int = 1
	var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left"

	if outputJson.SepSpaceNum != 0 {
		spaceNum = outputJson.SepSpaceNum
	}
	if outputJson.AlignmentM != "" {
		alignmentM = outputJson.AlignmentM
	}
	if outputJson.AlignmentSN != "" {
		alignmentSN = outputJson.AlignmentSN
	}
	if outputJson.AlignmentSV != "" {
		alignmentSV = outputJson.AlignmentSV
	}

	maxLength := math.MinInt64
	for _, coln := range outputJson.Cols {
		log.Trace("coln:", coln)

		if len(coln.Display) > maxLength {
			maxLength = len(coln.Display)
		}
		if coln.Length < len(coln.Display) {
			coln.Length = len(coln.Display)
		}

		colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display))
		colOutput = append(colOutput, coln)
	}
	fmtColName = formatLineBySpace(&colName, spaceNum)
	log.Trace("fmtColName:", fmtColName)

	var retFmtCols string
	var fmtColValues []string
	var numberResult int
	for _, colnvs := range cols.(map[string]interface{}) {
		log.Trace("colnvs:", colnvs)
		if colnvs == nil {
			break
		}
		numberResult = len(colnvs.([]interface{}))

		if numberResult == 1 && outputJson.SingleList == true {
			colnv := colnvs.([]interface{})[0]
			log.Trace("colnv:", colnv)

			var fmtNV []string
			for _, coln := range colOutput {
				fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display)
				log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName)
				value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
				fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value)
				fmtNV = append(fmtNV, fmtName+":  "+fmtValue)
			}

			fmtResults := strings.Join(fmtNV, "\n")
			log.Tracef("fmtResults:\n%s", fmtResults)
			fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
			retFmtCols = fmtResults + "\n\n" + fmtEnd
			log.Tracef("retFmtCols:\n%s", retFmtCols)
			return retFmtCols
		} else {
			for i := 0; i < numberResult; i++ {
				colnv := colnvs.([]interface{})[i]
				log.Trace("colnv:", colnv)
				var colValues []string
				var newVal []string
				for _, coln := range colOutput {
					value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
					if len(coln.Alias) != 0 {
						enumVal, _ := strconv.Atoi(value)
						value = parseEnumAlias(&(coln.Alias), enumVal)
					}
					newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value))
				}
				colValues = append(colValues, formatLineBySpace(&newVal, spaceNum))
				log.Trace("colValues:", colValues)
				fmtColValues = append(fmtColValues, strings.Join(colValues, "\n"))
				log.Trace("fmtColValues:", fmtColValues)
			}
			fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
			retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
			log.Tracef("retFmtCols:\n%s", retFmtCols)
			return retFmtCols
		}
	}
	fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
	retFmtCols = fmtColName + "\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
	log.Tracef("retFmtCols:\n%s", retFmtCols)
	return retFmtCols
}

func parseEnumAlias(alias *[]string, enumVal int) string {
	return (*alias)[enumVal]
}

func formatLineBySpace(strArray *[]string, spaceNum int) string {
	space := strings.Repeat(" ", spaceNum)
	return strings.Join(*strArray, space)
}

func ParseAlignmentOutput(length int, alignment string, str string) string {
	spaceLen := length - len(str)
	if spaceLen < 0 {
		log.Warnf("len(str=%s)=%d more length=%d", str, len(str), length)
		spaceLen = 0
	}
	var retStr string
	switch alignment {
	case "Left":
		suffix := strings.Repeat(" ", spaceLen)
		retStr = str + suffix
	case "Right":
		prefix := strings.Repeat(" ", spaceLen)
		retStr = prefix + str
		log.Tracef("retStr:%s", retStr)
	case "Middle":
		prefix := strings.Repeat(" ", int(math.Ceil(float64(spaceLen)/2)))
		suffix := strings.Repeat(" ", int(math.Floor(float64(spaceLen)/2)))
		retStr = prefix + str + suffix
	}
	log.Tracef("length=%d, spaceLne=%d, alignment=%s, str=%s, retStr=%s", length, spaceLen, alignment, str, retStr)
	return retStr
}

func ParseErrorOutput(err any) *[]byte {
	var output []byte

	var formatType string = DefaultFormatType
	if formatType == FormatTypeJson {
		output = []byte(fmt.Sprintf("ErrorCode = 1  Error message: %v\n\n", err))
	} else {
		output = []byte(fmt.Sprintf("RetCode = -1 operation failed: %v\n\n", err))
	}

	return &output
}

func formatTitle(title string) string {
	var builder strings.Builder
	builder.WriteString(title)
	builder.WriteString("\n")
	for i := 0; i < len(title); i++ {
		builder.WriteString("-")
	}
	builder.WriteString("\n")
	return builder.String()
}

func formatTableOutput() {

}
