/*
 * Copyright (C) 2014 ~ 2018 Deepin Technology Co., Ltd.
 *
 * Author:     jouyouyun <jouyouwen717@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package xsettings

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/linuxdeepin/go-gir/gio-2.0"
)

const (
	gsKeyTypeBool int = iota + 1
	gsKeyTypeInt
	gsKeyTypeString
	gsKeyTypeDouble
)

type typeGSKeyInfo struct {
	gsKey         string
	gsType        int
	xsKey         string
	xsType        *uint8
	convertGsToXs func(interface{}) (interface{}, error)
	convertXsToGs func(interface{}) (interface{}, error)
}

type typeGSKeyInfos []typeGSKeyInfo

var gsInfos = typeGSKeyInfos{
	{
		gsKey:  "theme-name",
		xsKey:  "Net/ThemeName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "icon-theme-name",
		xsKey:  "Net/IconThemeName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "fallback-icon-theme",
		xsKey:  "Net/FallbackIconTheme",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "sound-theme-name",
		xsKey:  "Net/SoundThemeName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-theme-name",
		xsKey:  "Gtk/ThemeName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-cursor-theme-name",
		xsKey:  "Gtk/CursorThemeName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-font-name",
		xsKey:  "Gtk/FontName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-key-theme-name",
		xsKey:  "Gtk/KeyThemeName",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-color-palette",
		xsKey:  "Gtk/ColorPalette",
		gsType: gsKeyTypeString,
	}, //deprecated
	{
		gsKey:  "gtk-toolbar-style",
		xsKey:  "Gtk/ToolbarStyle",
		gsType: gsKeyTypeString,
	}, //deprecated
	{
		gsKey:  "gtk-toolbar-icon-size",
		xsKey:  "Gtk/ToolbarIconSize",
		gsType: gsKeyTypeString,
	}, //deprecated
	{
		gsKey:  "gtk-color-scheme",
		xsKey:  "Gtk/ColorScheme",
		gsType: gsKeyTypeString,
	}, //deprecated
	{
		gsKey:  "gtk-im-preedit-style",
		xsKey:  "Gtk/IMPreeditStyle",
		gsType: gsKeyTypeString,
	}, //deprecated
	{
		gsKey:  "gtk-im-status-style",
		xsKey:  "Gtk/IMStatusStyle",
		gsType: gsKeyTypeString,
	}, //deprecated
	{
		gsKey:  "gtk-im-module",
		xsKey:  "Gtk/IMModule",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-modules",
		xsKey:  "Gtk/Modules",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "gtk-menubar-accel",
		xsKey:  "Gtk/MenuBarAccel",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "xft-hintstyle",
		xsKey:  "Xft/HintStyle",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "xft-rgba",
		xsKey:  "Xft/RGBA",
		gsType: gsKeyTypeString,
	},
	{
		gsKey:  "cursor-blink-time",
		xsKey:  "Net/CursorBlinkTime",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "gtk-cursor-blink-timeout",
		xsKey:  "Net/CursorBlinkTimeout",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "double-click-time",
		xsKey:  "Net/DoubleClickTime",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "double-click-distance",
		xsKey:  "Net/DoubleClickDistance",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "dnd-drag-threshold",
		xsKey:  "Net/DndDragThreshold",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  gsKeyGtkCursorThemeSize,
		xsKey:  "Gtk/CursorThemeSize",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "gtk-timeout-initial",
		xsKey:  "Gtk/TimeoutInitial",
		gsType: gsKeyTypeInt,
	}, //deprecated
	{
		gsKey:  "gtk-timeout-repeat",
		xsKey:  "Gtk/TimeoutRepeat",
		gsType: gsKeyTypeInt,
	}, //deprecated
	{
		gsKey:  "gtk-recent-files-max-age",
		xsKey:  "Gtk/RecentFilesMaxAge",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "xft-dpi",
		xsKey:  "Xft/DPI",
		gsType: gsKeyTypeInt,
	},
	{
		gsKey:  "cursor-blink",
		xsKey:  "Net/CursorBlink",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "enable-event-sounds",
		xsKey:  "Net/EnableEventSounds",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "enable-input-feedback-sounds",
		xsKey:  "Net/EnableInputFeedbackSounds",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "gtk-can-change-accels",
		xsKey:  "Gtk/CanChangeAccels",
		gsType: gsKeyTypeBool,
	}, //deprecated
	{
		gsKey:  "gtk-menu-images",
		xsKey:  "Gtk/MenuImages",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "gtk-button-images",
		xsKey:  "Gtk/ButtonImages",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "gtk-enable-animations",
		xsKey:  "Gtk/EnableAnimations",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "gtk-show-input-method-menu",
		xsKey:  "Gtk/ShowInputMethodMenu",
		gsType: gsKeyTypeBool,
	}, //deprecated
	{
		gsKey:  "gtk-show-unicode-menu",
		xsKey:  "Gtk/ShowUnicodeMenu",
		gsType: gsKeyTypeBool,
	}, //deprecated
	{
		gsKey:  "gtk-auto-mnemonics",
		xsKey:  "Gtk/AutoMnemonics",
		gsType: gsKeyTypeBool,
	}, //deprecated
	{
		gsKey:  "gtk-recent-files-enabled",
		xsKey:  "Gtk/RecentFilesEnabled",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "gtk-shell-shows-app-menu",
		xsKey:  "Gtk/ShellShowsAppMenu",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "xft-antialias",
		xsKey:  "Xft/Antialias",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:  "xft-hinting",
		xsKey:  "Xft/Hinting",
		gsType: gsKeyTypeBool,
	},
	{
		gsKey:         "qt-active-color",
		gsType:        gsKeyTypeString,
		xsKey:         "Qt/ActiveColor",
		xsType:        &settingTypeColorVar,
		convertGsToXs: convertStrToColor,
		convertXsToGs: convertColorToStr,
	},
	{
		gsKey:  "qt-font-name",
		gsType: gsKeyTypeString,
		xsKey:  "Qt/FontName",
	},
	{
		gsKey:  "qt-mono-font-name",
		gsType: gsKeyTypeString,
		xsKey:  "Qt/MonoFontName",
	},
	{
		gsKey:  "qt-font-point-size",
		gsType: gsKeyTypeDouble,
		xsKey:  "Qt/FontPointSize",
		// xsType is string
		convertGsToXs: convertDoubleToStr,
		convertXsToGs: convertStrToDouble,
	},
	{
		gsKey:  "dtk-window-radius",
		gsType: gsKeyTypeInt,
		xsKey:  "DTK/WindowRadius",
	},
	{
		gsKey:  "primary-monitor-name",
		gsType: gsKeyTypeString,
		xsKey:  "Gdk/PrimaryMonitorName",
	},
}

var settingTypeColorVar = settingTypeColor

func convertDoubleToStr(in interface{}) (interface{}, error) {
	value, ok := in.(float64)
	if !ok {
		return nil, errors.New("type is not float64")
	}
	return strconv.FormatFloat(value, 'f', -1, 64), nil
}

func convertStrToDouble(in interface{}) (interface{}, error) {
	value, ok := in.(string)
	if !ok {
		return nil, errors.New("type is not string")
	}
	return strconv.ParseFloat(value, 64)
}

func convertStrToColor(in interface{}) (interface{}, error) {
	str, ok := in.(string)
	if !ok {
		return nil, errors.New("type is not string")
	}

	fields := strings.Split(str, ",")
	if len(fields) != 4 {
		return nil, errors.New("length is not 4")
	}

	// R G B A
	var array [4]uint16
	for idx, field := range fields {
		fieldNum, err := strconv.ParseUint(field, 10, 16)
		if err != nil {
			return nil, err
		}
		array[idx] = uint16(fieldNum)
	}

	// 把值从 0~65535 范围内修正为 0~255 范围内
	for idx, value := range array {
		array[idx] = uint16((float64(value) / float64(math.MaxUint16)) * float64(math.MaxUint8))
	}
	return array, nil
}

func convertColorToStr(in interface{}) (interface{}, error) {
	array, ok := in.([4]uint16)
	if !ok {
		return nil, errors.New("type is not [4]uint16")
	}

	// 把值从 0~255 范围内修正为 0~65535 范围内
	for idx, value := range array {
		array[idx] = uint16((float64(value) / float64(math.MaxUint8) * float64(math.MaxUint16)))
	}

	return fmt.Sprintf("%d,%d,%d,%d", array[0], array[1],
		array[2], array[3]), nil
}

func (infos typeGSKeyInfos) getByGSKey(key string) *typeGSKeyInfo {
	for _, info := range infos {
		if key == info.gsKey {
			return &info
		}
	}

	return nil
}

func (infos typeGSKeyInfos) getByXSKey(key string) *typeGSKeyInfo {
	for _, info := range infos {
		if key == info.xsKey {
			return &info
		}
	}

	return nil
}

func (info *typeGSKeyInfo) getKeySType() uint8 {
	if info.xsType != nil {
		return *info.xsType
	}

	switch info.gsType {
	case gsKeyTypeBool, gsKeyTypeInt:
		return settingTypeInteger
	case gsKeyTypeString, gsKeyTypeDouble:
		return settingTypeString
	}

	return settingTypeInteger
}

func (info *typeGSKeyInfo) getValue(s *gio.Settings) (result interface{}, err error) {
	switch info.gsType {
	case gsKeyTypeBool:
		v := s.GetBoolean(info.gsKey)
		if v {
			result = int32(1)
		} else {
			result = int32(0)
		}
	case gsKeyTypeInt:
		result = int32(s.GetInt(info.gsKey))
	case gsKeyTypeString:
		result = s.GetString(info.gsKey)
	case gsKeyTypeDouble:
		result = s.GetDouble(info.gsKey)
	}

	if info.convertGsToXs != nil {
		result, err = info.convertGsToXs(result)
	}
	return
}

func (info *typeGSKeyInfo) setValue(s *gio.Settings, v interface{}) error {
	var err error
	if info.convertXsToGs != nil {
		v, err = info.convertXsToGs(v)
		if err != nil {
			return err
		}
	}

	switch info.gsType {
	case gsKeyTypeBool:
		tmp := v.(int32)
		if tmp == 1 {
			s.SetBoolean(info.gsKey, true)
		} else {
			s.SetBoolean(info.gsKey, false)
		}
	case gsKeyTypeInt:
		s.SetInt(info.gsKey, v.(int32))
	case gsKeyTypeString:
		s.SetString(info.gsKey, v.(string))
	case gsKeyTypeDouble:
		s.SetDouble(info.gsKey, v.(float64))
	}
	return nil
}
