// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package audio

import (
	"encoding/json"
	"io/ioutil"
	"path/filepath"
	"strings"

	dbus "github.com/godbus/dbus"
	bluez "github.com/linuxdeepin/go-dbus-factory/org.bluez"
	"github.com/linuxdeepin/go-lib/strv"
	"github.com/linuxdeepin/go-lib/xdg/basedir"
)

const (
	bluezModeA2dp      = "a2dp"      // 蓝牙A2DP模式（高质量音频）
	bluezModeHeadset   = "headset"   // 蓝牙耳机模式（通话+音频）
	bluezModeHandsfree = "handsfree" // 蓝牙免提模式（通话）
)

var (
	bluezModeFilterList 	 = []string{"a2dp_source"} // 蓝牙模式过滤列表
	bluezModeDefault         = bluezModeA2dp           // 默认蓝牙模式
	changeBluezModeForce     = true               	   // 是否强制切换蓝牙模式
)

// BluezAudioManager 蓝牙音频管理器
// 管理蓝牙音频设备的模式配置
type BluezAudioManager struct {
	BluezAudioConfig map[string]string // 蓝牙音频配置映射 (声卡名称 => 蓝牙模式)

	file string // 配置文件路径
}

// createBluezAudioManagerSingleton 创建蓝牙音频管理器单例工厂函数
// path: 配置文件路径
func createBluezAudioManagerSingleton(path string) func() *BluezAudioManager {
	var m *BluezAudioManager = nil
	return func() *BluezAudioManager {
		if m == nil {
			m = NewBluezAudioManager(path)
		}

		return m
	}
}

// 获取蓝牙音频管理器单例
// 由于蓝牙模式管理需要在很多个对象中使用，放在Audio对象中需要添加额外参数传递到各个模块很不方便，因此在此创建一个全局的单例
var bluezAudioConfigFilePath = filepath.Join(basedir.GetUserConfigDir(), "deepin/dde-daemon/bluezAudio.json")
var GetBluezAudioManager = createBluezAudioManagerSingleton(bluezAudioConfigFilePath)

// NewBluezAudioManager 创建蓝牙音频管理器实例
// path: 配置文件路径
func NewBluezAudioManager(path string) *BluezAudioManager {
	return &BluezAudioManager{
		BluezAudioConfig: make(map[string]string),
		file:             path,
	}
}

// Save 保存蓝牙音频配置到文件
// 将当前配置保存到JSON文件
func (m *BluezAudioManager) Save() {
	data, err := json.MarshalIndent(m.BluezAudioConfig, "", "  ")
	if err != nil {
		logger.Warning(err)
		return
	}

	err = ioutil.WriteFile(m.file, data, 0644)
	if err != nil {
		logger.Warning(err)
		return
	}
}

// Load 从文件加载蓝牙音频配置
// 从JSON文件加载配置信息
func (m *BluezAudioManager) Load() {
	data, err := ioutil.ReadFile(m.file)
	if err != nil {
		logger.Warning(err)
		return
	}

	err = json.Unmarshal(data, &m.BluezAudioConfig)
	if err != nil {
		logger.Warning(err)
		return
	}
}

// GetMode 获取蓝牙声卡模式
// cardName: 声卡名称，这里应该使用 *pulse.Card.Name
// 返回蓝牙模式，如果未配置则返回默认模式
func (m *BluezAudioManager) GetMode(cardName string) string {
	mode, ok := m.BluezAudioConfig[cardName]
	if ok {
		return mode
	} else {
		logger.Warningf("use the default mode %s", bluezModeDefault)
		return bluezModeDefault
	}
}

// SetMode 设置蓝牙声卡模式
// cardName: 声卡名称，这里应该使用 *pulse.Card.Name
// mode: 蓝牙模式
func (m *BluezAudioManager) SetMode(cardName string, mode string) {
	m.BluezAudioConfig[cardName] = mode
	m.Save()
}

// isBluezAudio 判断设备是否是蓝牙设备
// name: 设备名称，可以用声卡名，也可以用sink、端口等名称
// 返回是否为蓝牙音频设备
func isBluezAudio(name string) bool {
	return strings.Contains(strings.ToLower(name), "bluez")
}

// isBluezDeviceValid 判断蓝牙设备是否是音频设备
// bluezPath: 蓝牙设备的DBus路径
// 返回是否为有效的蓝牙音频设备
func isBluezDeviceValid(bluezPath string) bool {
	systemBus, err := dbus.SystemBus()
	if err != nil {
		logger.Warning("[isDeviceValid] dbus connect failed:", err)
		return false
	}
	bluezDevice, err := bluez.NewDevice(systemBus, dbus.ObjectPath(bluezPath))
	if err != nil {
		logger.Warning("[isDeviceValid] new device failed:", err)
		return false
	}
	icon, err := bluezDevice.Icon().Get(0)
	if err != nil {
		logger.Warning("[isDeviceValid] get icon failed:", err)
		return false
	}
	if icon == "computer" {
		return false
	}
	return true
}

// SetBluezMode 设置蓝牙声卡模式
// mode: 蓝牙模式
func (card *Card) SetBluezMode(mode string) {
	mode = strings.ToLower(mode)
	filterList := strv.Strv(bluezModeFilterList)

	for _, profile := range card.Profiles {
		v := strings.ToLower(profile.Name)
		if filterList.Contains(v) {
			logger.Debug("filter blue mode", v)
			continue
		}
		if profile.Available != 0 && strings.Contains(v, mode) {
			logger.Debugf("set %s to %s", card.core.Name, profile.Name)
			card.core.SetProfile(profile.Name)
			return
		}
	}
}

// AutoSetBluezMode 自动设置蓝牙声卡的模式
// 根据配置管理器中的设置自动选择蓝牙模式
func (card *Card) AutoSetBluezMode() {
	mode := GetBluezAudioManager().GetMode(card.core.Name)
	// 强制设置为推荐模式
	if mode != bluezModeDefault && changeBluezModeForce {
		logger.Infof("card %s force set bluez mode %s", card.core.Name, bluezModeDefault)
		find := false
		supportHandsfree  := false
		supportA2dp  	  := false
		for _, supportedMode := range card.BluezModeOpts() {
			if bluezModeDefault == supportedMode {
				mode = bluezModeDefault
				find = true
				break
			}
			if supportedMode == bluezModeHandsfree {
				supportHandsfree = true
			}
			if supportedMode == bluezModeA2dp {
				supportA2dp = true
			}
		}
		if !find {
		    // 如果不支持默认模式，则顺延 Handsfree > a2dp > headset
			if bluezModeDefault == bluezModeHandsfree && supportA2dp {
				mode = bluezModeA2dp
			} else if bluezModeDefault == bluezModeA2dp && supportHandsfree {
				mode = bluezModeHandsfree
			}
		}
		GetBluezAudioManager().SetMode(card.core.Name, mode)
	}
	logger.Debugf("card %s auto set bluez mode %s", card.core.Name, mode)
	card.SetBluezMode(mode)
}

// BluezMode 获取蓝牙声卡的模式(a2dp/headset)
// 返回当前活动的蓝牙模式
func (card *Card) BluezMode() string {
	profileName := strings.ToLower(card.ActiveProfile.Name)
	if strings.Contains(strings.ToLower(profileName), bluezModeA2dp) {
		return bluezModeA2dp
	} else if strings.Contains(strings.ToLower(profileName), bluezModeHeadset) {
		return bluezModeHeadset
	} else if strings.Contains(strings.ToLower(profileName), bluezModeHandsfree) {
		return bluezModeHandsfree
	} else {
		return ""
	}
}

// BluezModeOpts 获取蓝牙声卡的可用模式
// 返回所有可用的蓝牙模式列表
func (card *Card) BluezModeOpts() []string {
	opts := []string{}
	filterList := strv.Strv(bluezModeFilterList)
	for _, profile := range card.Profiles {
		if profile.Available == 0 {
			logger.Debugf("%s %s is unavailable", card.core.Name, profile.Name)
			continue
		}

		v := strings.ToLower(profile.Name)

		if filterList.Contains(v) {
			logger.Debug("filter bluez mode", v)
			continue
		}

		if strings.Contains(strings.ToLower(profile.Name), bluezModeA2dp) {
			opts = append(opts, bluezModeA2dp)
		}

		if strings.Contains(strings.ToLower(profile.Name), bluezModeHeadset) {
			opts = append(opts, bluezModeHeadset)
		}

		if strings.Contains(strings.ToLower(profile.Name), bluezModeHandsfree) {
			opts = append(opts, bluezModeHandsfree)
		}
	}
	return opts
}
