package sacloud

import (
	"encoding/json"
	"math"
	"strings"
	"time"
)

// MonitorValue アクティビティモニター
type MonitorValue struct {
	CPUTime         *float64 `json:"CPU-TIME,omitempty"`          // CPU時間
	Write           *float64 `json:",omitempty"`                  // ディスク書き込み
	Read            *float64 `json:",omitempty"`                  // ディスク読み取り
	Receive         *float64 `json:",omitempty"`                  // パケット受信
	Send            *float64 `json:",omitempty"`                  // パケット送信
	In              *float64 `json:",omitempty"`                  // パケット受信
	Out             *float64 `json:",omitempty"`                  // パケット送信
	TotalMemorySize *float64 `json:"Total-Memory-Size,omitempty"` // 総メモリサイズ
	UsedMemorySize  *float64 `json:"Used-Memory-Size,omitempty"`  // 使用済みメモリサイズ
	TotalDisk1Size  *float64 `json:"Total-Disk1-Size,omitempty"`  // 総ディスクサイズ
	UsedDisk1Size   *float64 `json:"Used-Disk1-Size,omitempty"`   // 使用済みディスクサイズ
	TotalDisk2Size  *float64 `json:"Total-Disk2-Size,omitempty"`  // 総ディスクサイズ
	UsedDisk2Size   *float64 `json:"Used-Disk2-Size,omitempty"`   // 使用済みディスクサイズ
	FreeDiskSize    *float64 `json:"Free-Disk-Size,omitempty"`    // 空きディスクサイズ(NFS)
	ResponseTimeSec *float64 `json:"responsetimesec,omitempty"`   // レスポンスタイム(シンプル監視)
	UplinkBPS       *float64 `json:"UplinkBps,omitempty"`         // 上り方向トラフィック
	DownlinkBPS     *float64 `json:"DownlinkBps"`                 // 下り方向トラフィック
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (m *MonitorValue) UnmarshalJSON(data []byte) error {
	targetData := strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1)
	if targetData == `[]` {
		return nil
	}

	tmp := &struct {
		CPUTime         *float64 `json:"CPU-TIME,omitempty"`
		Write           *float64 `json:",omitempty"`
		Read            *float64 `json:",omitempty"`
		Receive         *float64 `json:",omitempty"`
		Send            *float64 `json:",omitempty"`
		In              *float64 `json:",omitempty"`
		Out             *float64 `json:",omitempty"`
		TotalMemorySize *float64 `json:"Total-Memory-Size,omitempty"`
		UsedMemorySize  *float64 `json:"Used-Memory-Size,omitempty"`
		TotalDisk1Size  *float64 `json:"Total-Disk1-Size,omitempty"`
		UsedDisk1Size   *float64 `json:"Used-Disk1-Size,omitempty"`
		TotalDisk2Size  *float64 `json:"Total-Disk2-Size,omitempty"`
		UsedDisk2Size   *float64 `json:"Used-Disk2-Size,omitempty"`
		FreeDiskSize    *float64 `json:"Free-Disk-Size,omitempty"`
		ResponseTimeSec *float64 `json:"responsetimesec,omitempty"`
		UplinkBPS       *float64 `json:"UplinkBps,omitempty"`
		DownlinkBPS     *float64 `json:"DownlinkBps"`
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	m.CPUTime = tmp.CPUTime
	m.Write = tmp.Write
	m.Read = tmp.Read
	m.Receive = tmp.Receive
	m.Send = tmp.Send
	m.In = tmp.In
	m.Out = tmp.Out
	m.TotalMemorySize = tmp.TotalMemorySize
	m.UsedMemorySize = tmp.UsedMemorySize
	m.TotalDisk1Size = tmp.TotalDisk1Size
	m.UsedDisk1Size = tmp.UsedDisk1Size
	m.TotalDisk2Size = tmp.TotalDisk2Size
	m.UsedDisk2Size = tmp.UsedDisk2Size
	m.FreeDiskSize = tmp.FreeDiskSize
	m.ResponseTimeSec = tmp.ResponseTimeSec
	m.UplinkBPS = tmp.UplinkBPS
	m.DownlinkBPS = tmp.DownlinkBPS

	return nil
}

// ResourceMonitorRequest アクティビティモニター取得リクエスト
type ResourceMonitorRequest struct {
	Start *time.Time `json:",omitempty"` // 取得開始時間
	End   *time.Time `json:",omitempty"` // 取得終了時間
}

// NewResourceMonitorRequest アクティビティモニター取得リクエスト作成
func NewResourceMonitorRequest(start *time.Time, end *time.Time) *ResourceMonitorRequest {
	res := &ResourceMonitorRequest{}
	if start != nil {
		t := start.Truncate(time.Second)
		res.Start = &t
	}
	if end != nil {
		t := end.Truncate(time.Second)
		res.End = &t
	}
	return res
}

// ResourceMonitorResponse アクティビティモニターレスポンス
type ResourceMonitorResponse struct {
	Data *MonitorValues `json:",omitempty"` // メトリクス
}

// MonitorSummaryData メトリクスサマリー
type MonitorSummaryData struct {
	Max   float64 // 最大値
	Min   float64 // 最小値
	Avg   float64 // 平均値
	Count float64 // データ個数

}

// MonitorSummary アクティビティーモニター サマリー
type MonitorSummary struct {
	CPU  *MonitorSummaryData // CPU時間サマリー
	Disk *struct {           // ディスク利用サマリー
		Write *MonitorSummaryData // ディスク書き込みサマリー
		Read  *MonitorSummaryData // ディスク読み取りサマリー
	}
	Interface *struct { // NIC送受信サマリー
		Receive *MonitorSummaryData // 受信パケットサマリー
		Send    *MonitorSummaryData // 送信パケットサマリー
	}
}

// MonitorValues メトリクス リスト
type MonitorValues map[string]*MonitorValue

// FlatMonitorValue フラット化したメトリクス
type FlatMonitorValue struct {
	Time  time.Time // 対象時刻
	Value float64   // 値
}

// Calc サマリー計算
func (m *MonitorValues) Calc() *MonitorSummary {

	res := &MonitorSummary{}
	res.CPU = m.calcBy(func(v *MonitorValue) *float64 { return v.CPUTime })
	res.Disk = &struct {
		Write *MonitorSummaryData
		Read  *MonitorSummaryData
	}{
		Write: m.calcBy(func(v *MonitorValue) *float64 { return v.Write }),
		Read:  m.calcBy(func(v *MonitorValue) *float64 { return v.Read }),
	}
	res.Interface = &struct {
		Receive *MonitorSummaryData
		Send    *MonitorSummaryData
	}{
		Receive: m.calcBy(func(v *MonitorValue) *float64 { return v.Receive }),
		Send:    m.calcBy(func(v *MonitorValue) *float64 { return v.Send }),
	}

	return res
}

func (m *MonitorValues) calcBy(f func(m *MonitorValue) *float64) *MonitorSummaryData {
	res := &MonitorSummaryData{}
	var sum float64
	for _, data := range map[string]*MonitorValue(*m) {
		value := f(data)
		if value != nil {
			res.Count++
			res.Min = math.Min(res.Min, *value)
			res.Max = math.Max(res.Max, *value)
			sum += *value
		}
	}
	if sum > 0 && res.Count > 0 {
		res.Avg = sum / res.Count
	}

	return res
}

// FlattenCPUTimeValue フラット化 CPU時間
func (m *MonitorValues) FlattenCPUTimeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.CPUTime })
}

// FlattenDiskWriteValue フラット化 ディスク書き込み
func (m *MonitorValues) FlattenDiskWriteValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.Write })
}

// FlattenDiskReadValue フラット化 ディスク読み取り
func (m *MonitorValues) FlattenDiskReadValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.Read })
}

// FlattenPacketSendValue フラット化 パケット送信
func (m *MonitorValues) FlattenPacketSendValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.Send })
}

// FlattenPacketReceiveValue フラット化 パケット受信
func (m *MonitorValues) FlattenPacketReceiveValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.Receive })
}

// FlattenInternetInValue フラット化 パケット受信
func (m *MonitorValues) FlattenInternetInValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.In })
}

// FlattenInternetOutValue フラット化 パケット送信
func (m *MonitorValues) FlattenInternetOutValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.Out })
}

// FlattenTotalMemorySizeValue フラット化 総メモリサイズ
func (m *MonitorValues) FlattenTotalMemorySizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.TotalMemorySize })
}

// FlattenUsedMemorySizeValue フラット化 使用済みメモリサイズ
func (m *MonitorValues) FlattenUsedMemorySizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.UsedMemorySize })
}

// FlattenTotalDisk1SizeValue フラット化 総ディスクサイズ
func (m *MonitorValues) FlattenTotalDisk1SizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.TotalDisk1Size })
}

// FlattenUsedDisk1SizeValue フラット化 使用済みディスクサイズ
func (m *MonitorValues) FlattenUsedDisk1SizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.UsedDisk1Size })
}

// FlattenTotalDisk2SizeValue フラット化 総ディスクサイズ
func (m *MonitorValues) FlattenTotalDisk2SizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.TotalDisk2Size })
}

// FlattenUsedDisk2SizeValue フラット化 使用済みディスクサイズ
func (m *MonitorValues) FlattenUsedDisk2SizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.UsedDisk2Size })
}

// FlattenFreeDiskSizeValue フラット化 空きディスクサイズ(NFS)
func (m *MonitorValues) FlattenFreeDiskSizeValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.FreeDiskSize })
}

// FlattenResponseTimeSecValue フラット化 レスポンスタイム(シンプル監視)
func (m *MonitorValues) FlattenResponseTimeSecValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.ResponseTimeSec })
}

// FlattenUplinkBPSValue フラット化 上り方向トラフィック(セキュアモバイルSIM)
func (m *MonitorValues) FlattenUplinkBPSValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.UplinkBPS })
}

// FlattenDownlinkBPSValue フラット化 下り方向トライフィック(セキュアモバイルSIM)
func (m *MonitorValues) FlattenDownlinkBPSValue() ([]FlatMonitorValue, error) {
	return m.flattenValue(func(v *MonitorValue) *float64 { return v.DownlinkBPS })
}

func (m *MonitorValues) flattenValue(f func(*MonitorValue) *float64) ([]FlatMonitorValue, error) {
	var res []FlatMonitorValue

	for k, v := range map[string]*MonitorValue(*m) {
		if f(v) == nil {
			continue
		}
		time, err := time.Parse(time.RFC3339, k) // RFC3339 ≒ ISO8601
		if err != nil {
			return res, err
		}
		res = append(res, FlatMonitorValue{
			// Time
			Time: time,
			// Value
			Value: *f(v),
		})
	}
	return res, nil
}

// HasValue 取得したアクティビティーモニターに有効値が含まれるか判定
func (m *MonitorValue) HasValue() bool {
	values := []*float64{
		m.CPUTime,
		m.Read, m.Receive,
		m.Send, m.Write,
		m.In, m.Out,
		m.TotalMemorySize, m.UsedMemorySize,
		m.TotalDisk1Size, m.UsedDisk1Size,
		m.TotalDisk2Size, m.UsedDisk2Size,
		m.FreeDiskSize, m.ResponseTimeSec,
		m.UplinkBPS, m.DownlinkBPS,
	}
	for _, v := range values {
		if v != nil {
			return true
		}
	}
	return false
}
