package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
)

//-------------------------------------------------------------------------
// config
//
// Structure represents persistent config storage of the gocode daemon. Usually
// the config is located somewhere in ~/.config/gocode directory.
//-------------------------------------------------------------------------

type config struct {
	ProposeBuiltins    bool   `json:"propose-builtins"`
	LibPath            string `json:"lib-path"`
	CustomPkgPrefix    string `json:"custom-pkg-prefix"`
	CustomVendorDir    string `json:"custom-vendor-dir"`
	Autobuild          bool   `json:"autobuild"`
	ForceDebugOutput   string `json:"force-debug-output"`
	PackageLookupMode  string `json:"package-lookup-mode"`
	CloseTimeout       int    `json:"close-timeout"`
	UnimportedPackages bool   `json:"unimported-packages"`
}

var g_config = config{
	ProposeBuiltins:    false,
	LibPath:            "",
	CustomPkgPrefix:    "",
	Autobuild:          false,
	ForceDebugOutput:   "",
	PackageLookupMode:  "go",
	CloseTimeout:       1800,
	UnimportedPackages: false,
}

var g_string_to_bool = map[string]bool{
	"t":     true,
	"true":  true,
	"y":     true,
	"yes":   true,
	"on":    true,
	"1":     true,
	"f":     false,
	"false": false,
	"n":     false,
	"no":    false,
	"off":   false,
	"0":     false,
}

func set_value(v reflect.Value, value string) {
	switch t := v; t.Kind() {
	case reflect.Bool:
		v, ok := g_string_to_bool[value]
		if ok {
			t.SetBool(v)
		}
	case reflect.String:
		t.SetString(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v, err := strconv.ParseInt(value, 10, 64)
		if err == nil {
			t.SetInt(v)
		}
	case reflect.Float32, reflect.Float64:
		v, err := strconv.ParseFloat(value, 64)
		if err == nil {
			t.SetFloat(v)
		}
	}
}

func list_value(v reflect.Value, name string, w io.Writer) {
	switch t := v; t.Kind() {
	case reflect.Bool:
		fmt.Fprintf(w, "%s %v\n", name, t.Bool())
	case reflect.String:
		fmt.Fprintf(w, "%s \"%v\"\n", name, t.String())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fmt.Fprintf(w, "%s %v\n", name, t.Int())
	case reflect.Float32, reflect.Float64:
		fmt.Fprintf(w, "%s %v\n", name, t.Float())
	}
}

func (this *config) list() string {
	str, typ := this.value_and_type()
	buf := bytes.NewBuffer(make([]byte, 0, 256))
	for i := 0; i < str.NumField(); i++ {
		v := str.Field(i)
		name := typ.Field(i).Tag.Get("json")
		list_value(v, name, buf)
	}
	return buf.String()
}

func (this *config) list_option(name string) string {
	str, typ := this.value_and_type()
	buf := bytes.NewBuffer(make([]byte, 0, 256))
	for i := 0; i < str.NumField(); i++ {
		v := str.Field(i)
		nm := typ.Field(i).Tag.Get("json")
		if nm == name {
			list_value(v, name, buf)
		}
	}
	return buf.String()
}

func (this *config) set_option(name, value string) string {
	str, typ := this.value_and_type()
	buf := bytes.NewBuffer(make([]byte, 0, 256))
	for i := 0; i < str.NumField(); i++ {
		v := str.Field(i)
		nm := typ.Field(i).Tag.Get("json")
		if nm == name {
			set_value(v, value)
			list_value(v, name, buf)
		}
	}
	this.write()
	return buf.String()

}

func (this *config) value_and_type() (reflect.Value, reflect.Type) {
	v := reflect.ValueOf(this).Elem()
	return v, v.Type()
}

func (this *config) write() error {
	data, err := json.Marshal(this)
	if err != nil {
		return err
	}

	// make sure config dir exists
	dir := config_dir()
	if !file_exists(dir) {
		os.MkdirAll(dir, 0755)
	}

	f, err := os.Create(config_file())
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(data)
	if err != nil {
		return err
	}

	return nil
}

func (this *config) read() error {
	data, err := ioutil.ReadFile(config_file())
	if err != nil {
		return err
	}

	err = json.Unmarshal(data, this)
	if err != nil {
		return err
	}

	return nil
}
