package sysconf

import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type CSV struct {
	header []string
	text   [][]string
}

type CSVRow struct {
	header []string
	data   []string
}

type CSVValue struct {
	key   string
	value string
}

func ParseCSV(data []byte, hasHeader bool) (csv CSV, err error) {
	strData := strings.Split(string(bytes.TrimSpace(data)), "\n")
	if len(strData) < 1 {
		err = fmt.Errorf("cannot parse data,invalid data format")
	}
	var header []string
	var text [][]string
	if hasHeader {
		header = csvAnalyse(strData[0])
		strData = strData[1:]
	} else {
		num := len(csvAnalyse(strData[0]))
		for i := 0; i < num; i++ {
			header = append(header, strconv.Itoa(i))
		}
	}
	for k, v := range strData {
		tmpData := csvAnalyse(v)
		if len(tmpData) != len(header) {
			err = fmt.Errorf("cannot parse data line %d,got %d values but need %d", k, len(tmpData), len(header))
			return
		}
		text = append(text, tmpData)
	}
	csv.header = header
	csv.text = text
	return
}

func (csv *CSV) Header() []string {
	return csv.header
}

func (csv *CSV) Data() [][]string {
	return csv.text
}

func (csv *CSV) Row(row int) *CSVRow {
	if row >= len(csv.Data()) {
		return nil
	}
	return &CSVRow{
		header: csv.Header(),
		data:   csv.Data()[row],
	}
}

func (csv *CSVRow) Get(key string) *CSVValue {
	for k, v := range csv.header {
		if v == key {
			return &CSVValue{
				key:   key,
				value: csv.data[k],
			}
		}
	}
	return nil
}

func (csv *CSVRow) Col(key int) *CSVValue {
	if key >= len(csv.header) {
		return nil
	}
	return &CSVValue{
		key:   csv.header[key],
		value: csv.data[key],
	}
}

func (csv *CSVRow) Header() []string {
	return csv.header
}

func (csv *CSV) MapData() []map[string]string {
	var result []map[string]string
	for _, v := range csv.text {
		tmp := make(map[string]string)
		for k, v2 := range csv.header {
			tmp[v2] = v[k]
		}
		result = append(result, tmp)
	}
	return result
}

func CsvAnalyse(data string) []string {
	return csvAnalyse(data)
}

func csvAnalyse(data string) []string {
	var segStart bool = false
	var segReady bool = false
	var segSign string = ""
	var dotReady bool = false
	data = strings.TrimSpace(data)
	var result []string
	var seg string
	for k, v := range []rune(data) {
		if k == 0 && v != []rune(`"`)[0] {
			dotReady = true
		}
		if v != []rune(`,`)[0] && dotReady {
			segSign = `,`
			segStart = true
			dotReady = false
			if v == []rune(`"`)[0] {
				segSign = `"`
				continue
			}
		}
		if dotReady && v == []rune(`,`)[0] {
			//dotReady = false
			result = append(result, "")
			continue
		}

		if v == []rune(`"`)[0] && segStart {
			if !segReady {
				segReady = true
				continue
			}
			seg += `"`
			segReady = false
			continue
		}
		if segReady && segSign == `"` && segStart {
			segReady = false
			segStart = false
			result = append(result, seg)
			segSign = ``
			seg = ""
		}

		if v == []rune(`"`)[0] && !segStart {
			segStart = true
			segReady = false
			segSign = `"`
			continue
		}
		if v == []rune(`,`)[0] && !segStart {
			dotReady = true
		}
		if v == []rune(`,`)[0] && segStart && segSign == "," {
			segStart = false
			result = append(result, seg)
			dotReady = true
			segSign = ``
			seg = ""
		}
		if segStart {
			seg = string(append([]rune(seg), v))
		}
	}
	if len(data) != 0 && len(result) == 0 && seg == "" {
		result = append(result, data)
	} else {
		result = append(result, seg)
	}

	return result
}

func MarshalCSV(header []string, ins interface{}) ([]byte, error) {
	var result [][]string
	t := reflect.TypeOf(ins)
	v := reflect.ValueOf(ins)
	if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
		return nil, errors.New("not a Slice or Array")
	}
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		v = v.Elem()
	}

	for i := 0; i < v.Len(); i++ {
		subT := reflect.TypeOf(v.Index(i).Interface())
		subV := reflect.ValueOf(v.Index(i).Interface())
		if subV.Kind() == reflect.Slice || subV.Kind() == reflect.Array {
			if subT.Kind() == reflect.Ptr {
				subV = subV.Elem()
			}
			var tmp []string
			for j := 0; j < subV.Len(); j++ {
				tmp = append(tmp, fmt.Sprint(reflect.ValueOf(subV.Index(j))))
			}
			result = append(result, tmp)
		}
		if subV.Kind() == reflect.Struct {
			var tmp []string
			if subT.Kind() == reflect.Ptr {
				subV = subV.Elem()
			}
			for i := 0; i < subV.NumField(); i++ {
				tmp = append(tmp, fmt.Sprint(subV.Field(i)))
			}
			result = append(result, tmp)
		}
	}

	return buildCSV(header,result)
}

func buildCSV(header []string, data [][]string) ([]byte, error) {
	var result []string
	var length int
	build := func(slc []string) string {
		for k, v := range slc {
			if strings.Index(v, `"`) >= 0 {
				v = strings.ReplaceAll(v, `"`, `""`)
			}
			if strings.Index(v,"\n")>=0 {
				v=strings.ReplaceAll(v,"\n",`\n`)
			}
			if strings.Index(v,"\r")>=0 {
				v=strings.ReplaceAll(v,"\r",`\r`)
			}
			v = `"` + v + `"`
			slc[k] = v
		}
		return strings.Join(slc, ",")
	}
	if len(header) != 0 {
		result = append(result, build(header))
		length = len(header)
	} else {
		length = len(data[0])
	}
	for k, v := range data {
		if len(v) != length {
			return nil, fmt.Errorf("line %d got length %d ,but need %d", k, len(v), length)
		}
		result = append(result, build(v))
	}
	return []byte(strings.Join(result, "\n")), nil
}