/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>
*/

package utils

import (
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"
)

// Defines years days series
type Years []int

func (ys Years) Sort() {
	sort.Sort(ys)
}

func (ys Years) Len() int {
	return len(ys)
}

func (ys Years) Swap(i, j int) {
	ys[i], ys[j] = ys[j], ys[i]
}

func (ys Years) Less(j, i int) bool {
	return ys[j] < ys[i]
}

// Return true if the specified date is inside the series
func (ys Years) Contains(year int) (result bool) {
	result = false
	for _, yss := range ys {
		if yss == year {
			result = true
			break
		}
	}
	return
}

// Parse Years elements from string separated by sep.
func (ys *Years) Parse(input, sep string) {
	switch input {
	case MetaAny, EmptyString:
		*ys = []int{}
	default:
		elements := strings.Split(input, sep)
		for _, yss := range elements {
			if year, err := strconv.Atoi(yss); err == nil {
				*ys = append(*ys, year)
			}
		}
	}
}

func (ys Years) Serialize(sep string) string {
	if len(ys) == 0 {
		return MetaAny
	}
	var yStr string
	for idx, yr := range ys {
		if idx != 0 {
			yStr = fmt.Sprintf("%s%s%d", yStr, sep, yr)
		} else {
			yStr = strconv.Itoa(yr)
		}
	}
	return yStr
}

// Equals implies that Years are already sorted
func (ys Years) Equals(oYS Years) bool {
	if len(ys) != len(oYS) {
		return false
	}
	for i := range ys {
		if ys[i] != oYS[i] {
			return false
		}
	}
	return true
}

// Defines months series
type Months []time.Month

func (m Months) Sort() {
	sort.Sort(m)
}

func (m Months) Len() int {
	return len(m)
}

func (m Months) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}

func (m Months) Less(j, i int) bool {
	return m[j] < m[i]
}

// Return true if the specified date is inside the series
func (m Months) Contains(month time.Month) (result bool) {
	for _, ms := range m {
		if ms == month {
			result = true
			break
		}
	}
	return
}

// Loades Month elemnents from a string separated by sep.
func (m *Months) Parse(input, sep string) {
	switch input {
	case MetaAny, EmptyString: // Apier cannot receive empty string, hence using meta-tag
		*m = []time.Month{}
	default:
		elements := strings.Split(input, sep)
		for _, ms := range elements {
			if month, err := strconv.Atoi(ms); err == nil {
				*m = append(*m, time.Month(month))
			}
		}
	}
}

// Dumps the months in a serialized string, similar to the one parsed
func (m Months) Serialize(sep string) string {
	if len(m) == 0 {
		return MetaAny
	}
	var mStr string
	for idx, mt := range m {
		if idx != 0 {
			mStr = fmt.Sprintf("%s%s%d", mStr, sep, mt)
		} else {
			mStr = strconv.Itoa(int(mt))
		}
	}
	return mStr
}

func (m Months) IsComplete() bool {
	allMonths := Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}
	m.Sort()
	return reflect.DeepEqual(m, allMonths)
}

// Equals implies that Months are already sorted
func (m Months) Equals(oM Months) bool {
	if len(m) != len(oM) {
		return false
	}
	for i := range m {
		if m[i] != oM[i] {
			return false
		}
	}
	return true
}

// Defines month days series
type MonthDays []int

func (md MonthDays) Sort() {
	sort.Sort(md)
}

func (md MonthDays) Len() int {
	return len(md)
}

func (md MonthDays) Swap(i, j int) {
	md[i], md[j] = md[j], md[i]
}

func (md MonthDays) Less(j, i int) bool {
	return md[j] < md[i]
}

// Return true if the specified date is inside the series
func (md MonthDays) Contains(monthDay int) (result bool) {
	result = false
	for _, mds := range md {
		if mds == monthDay {
			result = true
			break
		}
	}
	return
}

// Parse MonthDay elements from string separated by sep.
func (md *MonthDays) Parse(input, sep string) {
	switch input {
	case MetaAny, EmptyString:
		*md = []int{}
	default:
		elements := strings.Split(input, sep)
		for _, mds := range elements {
			if day, err := strconv.Atoi(mds); err == nil {
				*md = append(*md, day)
			}
		}
	}
}

// Dumps the month days in a serialized string, similar to the one parsed
func (md MonthDays) Serialize(sep string) string {
	if len(md) == 0 {
		return MetaAny
	}
	var mdsStr string
	for idx, mDay := range md {
		if idx != 0 {
			mdsStr = fmt.Sprintf("%s%s%d", mdsStr, sep, mDay)
		} else {
			mdsStr = strconv.Itoa(mDay)
		}
	}
	return mdsStr
}

// Equals implies that MonthDays are already sorted
func (md MonthDays) Equals(oMD MonthDays) bool {
	if len(md) != len(oMD) {
		return false
	}
	for i := range md {
		if md[i] != oMD[i] {
			return false
		}
	}
	return true
}

// Defines week days series
type WeekDays []time.Weekday

func (wd WeekDays) Sort() {
	sort.Sort(wd)
}

func (wd WeekDays) Len() int {
	return len(wd)
}

func (wd WeekDays) Swap(i, j int) {
	wd[i], wd[j] = wd[j], wd[i]
}

func (wd WeekDays) Less(j, i int) bool {
	return wd[j] < wd[i]
}

// Return true if the specified date is inside the series
func (wd WeekDays) Contains(weekDay time.Weekday) (result bool) {
	result = false
	for _, wds := range wd {
		if wds == weekDay {
			result = true
			break
		}
	}
	return
}

func (wd *WeekDays) Parse(input, sep string) {
	switch input {
	case MetaAny, EmptyString:
		*wd = []time.Weekday{}
	default:
		elements := strings.Split(input, sep)
		for _, wds := range elements {
			if day, err := strconv.Atoi(wds); err == nil {
				*wd = append(*wd, time.Weekday(day%7)) // %7 for sunday = 7 normalization
			}
		}
	}
}

// Dumps the week days in a serialized string, similar to the one parsed
func (wd WeekDays) Serialize(sep string) string {
	if len(wd) == 0 {
		return MetaAny
	}
	var wdStr string
	for idx, d := range wd {
		if idx != 0 {
			wdStr = fmt.Sprintf("%s%s%d", wdStr, sep, d)
		} else {
			wdStr = strconv.Itoa(int(d))
		}
	}
	return wdStr
}

// Equals implies that WeekDays are already sorted
func (wd WeekDays) Equals(oWD WeekDays) bool {
	if len(wd) != len(oWD) {
		return false
	}
	for i := range wd {
		if wd[i] != oWD[i] {
			return false
		}
	}
	return true
}

func DaysInMonth(year int, month time.Month) float64 {
	return float64(time.Date(year, month, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 1, -1).Day())
}

func DaysInYear(year int) float64 {
	first := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
	last := first.AddDate(1, 0, 0)
	return float64(last.Sub(first).Hours() / 24)
}
