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

package audio

import (
	"fmt"
	"strconv"
	"sync"

	dbus "github.com/godbus/dbus"
	"github.com/linuxdeepin/go-lib/dbusutil"
	"github.com/linuxdeepin/go-lib/pulse"
)

// Source 音频输入设备结构体
// 代表一个音频输入设备，如麦克风、线路输入等
type Source struct {
	audio       *Audio
	service     *dbusutil.Service
	PropsMu     sync.RWMutex
	index       uint32
	cVolume     pulse.CVolume // 脉冲音频音量对象
	channelMap  pulse.ChannelMap // 声道映射
	Name        string // 设备名称
	Description string // 设备描述
	// 默认的输入音量
	BaseVolume     float64
	Mute           bool // 是否静音
	Volume         float64 // 当前音量
	Balance        float64 // 左右声道平衡值
	SupportBalance bool // 是否支持左右声道调整
	Fade           float64 // 前后声道平衡值
	SupportFade    bool // 是否支持前后声道调整
	// dbusutil-gen: equal=portsEqual
	Ports      []Port // 支持的输入端口
	ActivePort Port   // 当前使用的输入端口
	// 声卡的索引
	Card uint32
}

// newSource 创建新的输入设备实例
// sourceInfo: 脉冲音频输入设备信息
// audio: 音频管理实例
func newSource(sourceInfo *pulse.Source, audio *Audio) *Source {
	s := &Source{
		audio:   audio,
		index:   sourceInfo.Index,
		service: audio.service,
	}
	if !isPhysicalDevice(sourceInfo.Name) {
		masterSourceInfo := audio.getSourceInfoByName(sourceInfo.Proplist["device.master_device"])
		if masterSourceInfo == nil {
			logger.Warningf("cannot get master source for %s", sourceInfo.Name)
		} else {
			sourceInfo.Card = masterSourceInfo.Card
			sourceInfo.Ports = masterSourceInfo.Ports
			sourceInfo.ActivePort = masterSourceInfo.ActivePort
			logger.Debugf("create reducing noise source on %s", masterSourceInfo.Name)
		}
	}

	s.update(sourceInfo)
	return s
}

// CheckPort 检测端口是否被禁用
// 检查当前活动端口是否可用
func (s *Source) CheckPort() *dbus.Error {
	enabled, err := s.audio.IsPortEnabled(s.Card, s.ActivePort.Name)
	if err != nil {
		return err
	}

	if !enabled {
		return dbusutil.ToError(fmt.Errorf("port<%d:%s> is disabled", s.Card, s.ActivePort.Name))
	}

	return nil
}

// SetVolume 设置输入音量大小
// value: 音量大小 (0.0 - 1.0)
// isPlay: 是否播放声音反馈
func (s *Source) SetVolume(value float64, isPlay bool) *dbus.Error {
	logger.Infof("dbus call SetVolume with value %f and isPlay %t, the source name is %s",
		value, isPlay, s.Name)

	err := s.CheckPort()
	if err != nil {
		logger.Warning(err.Body...)
		return err
	}

	if !isVolumeValid(value) {
		err1 := fmt.Errorf("invalid volume value: %v", value)
		logger.Warning(err.Body...)
		return dbusutil.ToError(err1)
	}

	if value == 0 {
		value = 0.001
		s.SetMute(true)
	} else if s.Mute {
		s.SetMute(false)
	}
	s.PropsMu.RLock()
	cv := s.cVolume.SetAvg(value)
	s.PropsMu.RUnlock()
	s.audio.context().SetSourceVolumeByIndex(s.index, cv)

	GetConfigKeeper().SetVolume(s.audio.getCardNameById(s.Card), s.ActivePort.Name, value)

	if isPlay {
		playFeedback()
	}
	return nil
}

// SetBalance 设置左右声道平衡值
// value: 声道平衡值 (-1.0 到 1.0)
// isPlay: 是否播放声音反馈
func (s *Source) SetBalance(value float64, isPlay bool) *dbus.Error {
	logger.Infof("dbus call SetBalance with value %f and isPlay %t, the source name is %s",
		value, isPlay, s.Name)

	err := s.CheckPort()
	if err != nil {
		logger.Warning(err.Body...)
		return err
	}

	if value < -1.00 || value > 1.00 {
		err1 := fmt.Errorf("invalid volume value: %v", value)
		logger.Warning(err1)
		return dbusutil.ToError(err1)
	}

	s.PropsMu.RLock()
	cv := s.cVolume.SetBalance(s.channelMap, value)
	s.PropsMu.RUnlock()
	s.audio.context().SetSourceVolumeByIndex(s.index, cv)

	GetConfigKeeper().SetBalance(s.audio.getCardNameById(s.Card), s.ActivePort.Name, value)

	if isPlay {
		playFeedback()
	}
	return nil
}

// SetFade 设置前后声道平衡值
// value: 声道平衡值 (-1.0 到 1.0)
func (s *Source) SetFade(value float64) *dbus.Error {
	logger.Infof("dbus call SetFade with value %f, the source name is %s", value, s.Name)

	err := s.CheckPort()
	if err != nil {
		logger.Warning(err.Body...)
		return err
	}

	if value < -1.00 || value > 1.00 {
		err1 := fmt.Errorf("invalid volume value: %v", value)
		logger.Warning(err1)
		return dbusutil.ToError(err1)
	}

	s.PropsMu.RLock()
	cv := s.cVolume.SetFade(s.channelMap, value)
	s.PropsMu.RUnlock()
	s.audio.context().SetSourceVolumeByIndex(s.index, cv)

	playFeedback()
	return nil
}

// setVBF 设置同时设置音量和平衡
// v: volume音量值
// b: balance左右平衡值
// f: fade前后平衡值
func (s *Source) setVBF(v, b, f float64) *dbus.Error {
	if v < -1.00 || v > 1.00 {
		return dbusutil.ToError(fmt.Errorf("invalid volume value: %v", v))
	}

	if b < -1.00 || b > 1.00 {
		return dbusutil.ToError(fmt.Errorf("invalid balance value: %v", b))
	}

	if f < -1.00 || b > 1.00 {
		return dbusutil.ToError(fmt.Errorf("invalid fade value: %v", f))
	}

	s.PropsMu.RLock()
	cv := s.cVolume.SetAvg(v)
	cv = cv.SetBalance(s.channelMap, b)
	cv = cv.SetFade(s.channelMap, f)
	s.PropsMu.RUnlock()
	s.audio.context().SetSourceVolumeByIndex(s.index, cv)

	return nil
}

func (s *Source) SetMuteWithPlayFeedback(value bool, play bool) error {
	logger.Infof("SetMuteWithPlayFeedback value %t, the source name is %s, is play feedback : %v", value, s.Name, play)

	err := s.CheckPort()
	if err != nil {
		logger.Warning(err.Body...)
		return err
	}

	s.audio.context().SetSourceMuteByIndex(s.index, value)
	if s.audio.globalMuteEnabled {
		GetConfigKeeper().SetMuteInput(value)
	} else {
		GetConfigKeeper().SetMute(s.audio.getCardNameById(s.Card), s.ActivePort.Name, value)
	}

	if !value && play {
		playFeedback()
	}
	return nil
}

// SetMute 设置静音状态
// value: 是否静音
func (s *Source) SetMute(value bool) *dbus.Error {
	logger.Infof("dbus call SetMute with value %t, the source name is %s", value, s.Name)
	return dbusutil.ToError(s.SetMuteWithPlayFeedback(value, true))
}

func (s *Source) SetPort(name string) *dbus.Error {
	logger.Infof("dbus call SetPort with name %s, the source name is %s", name, s.Name)

	s.audio.context().SetSourcePortByIndex(s.index, name)
	return nil
}

func (s *Source) GetMeter() (meter dbus.ObjectPath, busErr *dbus.Error) {
	logger.Infof("dbus call GetMeter, the source name is %s", s.Name)

	id := fmt.Sprintf("source%d", s.index)
	s.audio.mu.Lock()
	m, ok := s.audio.meters[id]
	s.audio.mu.Unlock()
	if ok {
		return m.getPath(), nil
	}

	sourceMeter := pulse.NewSourceMeter(s.audio.ctx, s.index)
	m = newMeter(id, sourceMeter, s.audio)
	meterPath := m.getPath()
	err := s.service.Export(meterPath, m)
	if err != nil {
		logger.Warning(err)
		return "/", dbusutil.ToError(err)
	}

	s.audio.mu.Lock()
	s.audio.meters[id] = m
	s.audio.mu.Unlock()

	m.core.ConnectChanged(func(v float64) {
		m.PropsMu.Lock()
		m.setPropVolume(v)
		m.PropsMu.Unlock()
	})
	return meterPath, nil
}

func (s *Source) getPath() dbus.ObjectPath {
	return dbus.ObjectPath(dbusPath + "/Source" + strconv.Itoa(int(s.index)))
}

func (*Source) GetInterfaceName() string {
	return dbusInterface + ".Source"
}

func (s *Source) update(sourceInfo *pulse.Source) {
	if s == nil {
		return
	}
	// 如果是虚拟通道，则将card和ports等设为对应主通道的值，这是为了能够正常使用降噪的训通道
	if !isPhysicalDevice(sourceInfo.Name) {
		masterSourceInfo := s.audio.getSourceInfoByName(sourceInfo.Proplist["device.master_device"])
		if masterSourceInfo == nil {
			logger.Warningf("cannot get master source for %s", sourceInfo.Name)
		} else {
			sourceInfo.Card = masterSourceInfo.Card
			sourceInfo.Ports = masterSourceInfo.Ports
			sourceInfo.ActivePort = masterSourceInfo.ActivePort
			logger.Debugf("create reducing noise source on %s", masterSourceInfo.Name)
		}
	}

	s.PropsMu.Lock()
	s.cVolume = sourceInfo.Volume
	s.channelMap = sourceInfo.ChannelMap
	s.Name = sourceInfo.Name
	s.Description = sourceInfo.Description
	s.Card = sourceInfo.Card
	s.BaseVolume = sourceInfo.BaseVolume.ToPercent()

	s.setPropVolume(floatPrecision(sourceInfo.Volume.Avg()))
	if (s.setPropMute(sourceInfo.Mute)) {
		s.audio.setMuteLED(nodeF4KeyLED, sourceInfo.Mute)
		s.audio.setMuteLED(nodeF7KeyLED, sourceInfo.Mute)
	}

	//TODO: handle this
	s.setPropSupportFade(false)
	s.setPropFade(sourceInfo.Volume.Fade(sourceInfo.ChannelMap))
	s.setPropSupportBalance(true)
	s.setPropBalance(sourceInfo.Volume.Balance(sourceInfo.ChannelMap))

	var ports []Port
	for _, p := range sourceInfo.Ports {
		ports = append(ports, toPort(p))
	}

	s.setPropPorts(ports)
	activePortChanged := s.setPropActivePort(toPort(sourceInfo.ActivePort))

	s.PropsMu.Unlock()

	if activePortChanged && s.audio.defaultSourceName == s.Name {
		logger.Debugf("default source update active port %s", sourceInfo.ActivePort.Name)
		s.audio.resumeSourceConfig(s, isPhysicalDevice(s.Name))
	}
}

func (s *Source) setMute(v bool) {
	logger.Debugf("Source #%d setMute %v", s.index, v)
	s.audio.context().SetSourceMuteByIndex(s.index, v)
}
