//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
//Copyright (C) 2024 HTWKalender support@htwkalender.de

//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 ical

import (
	"htwkalender/ical/model"
	"htwkalender/ical/service/functions"
	clock "htwkalender/ical/service/functions/time"
	"htwkalender/ical/service/names"
	"time"

	"github.com/jordic/goics"
	_ "time/tzdata"
)

// IcalModel local type for EmitICal function
type IcalModel struct {
	Events  model.Events
	Mapping map[string]model.FeedCollection
}

// EmitICal implements the interface for goics
func (icalModel IcalModel) EmitICal() goics.Componenter {
	internalClock := clock.RealClock{}
	c := generateIcalEmit(icalModel, internalClock)
	return c
}

func generateIcalEmit(icalModel IcalModel, internalClock clock.Clock) *goics.Component {
	europeTime, _ := time.LoadLocation("Europe/Berlin")
	c := goics.NewComponent()
	c.SetType("VCALENDAR")
	// PRODID is required by the standard
	c.AddProperty("PRODID", "-//HTWK Kalender//htwkalender.de//DE")

	c.AddProperty("VERSION", "2.0")
	c.AddProperty("CALSCALE", "GREGORIAN")
	c.AddProperty("TZID", "EUROPE/BERLIN")
	c.AddProperty("X-WR-CALNAME", "HTWK Kalender")
	c.AddProperty("X-WR-TIMEZONE", "EUROPE/BERLIN")
	//add v time zone
	icalModel.vtimezone(c)

	for _, event := range icalModel.Events {
		mapEntry, mappingFound := icalModel.Mapping[event.UUID]

		s := goics.NewComponent()
		s.SetType("VEVENT")

		s.AddProperty(goics.FormatDateTime("DTSTAMP", internalClock.Now().Local().In(europeTime)))

		// create a unique id for the event by hashing the event start, end, course and name
		var eventHash = functions.HashString(time.Time(event.Start).String() + time.Time(event.End).String() + event.Course + event.Name + event.Rooms)

		s.AddProperty("UID", eventHash+"@htwkalender.de")
		s.AddProperty(goics.FormatDateTime("DTEND", time.Time(event.End).Local().In(europeTime)))
		s.AddProperty(goics.FormatDateTime("DTSTART", time.Time(event.Start).Local().In(europeTime)))

		if mappingFound {
			addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry))
			addAlarmIfSpecified(s, event, mapEntry, internalClock)
		} else {
			addPropertyIfNotEmpty(s, "SUMMARY", event.Name)
		}

		addPropertyIfNotEmpty(s, "DESCRIPTION", generateDescription(event))
		addPropertyIfNotEmpty(s, "LOCATION", event.Rooms)
		c.AddComponent(s)
	}
	return c
}

func (icalModel IcalModel) vtimezone(c *goics.Component) {
	tz := goics.NewComponent()
	tz.SetType("VTIMEZONE")
	tz.AddProperty("TZID", "EUROPE/BERLIN")
	//add standard time
	icalModel.standard(tz)
	//add daylight time
	icalModel.daylight(tz)

	c.AddComponent(tz)
}

func (icalModel IcalModel) standard(tz *goics.Component) {
	st := NewHtwkalenderComponent()
	st.SetType("STANDARD")
	st.AddProperty("DTSTART", "19701025T030000")
	st.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU")
	st.AddProperty("TZOFFSETFROM", "+0200")
	st.AddProperty("TZOFFSETTO", "+0100")
	st.AddProperty("TZNAME", "CET")
	tz.AddComponent(st)
}

// create an override for goics component function Write
// to add the RRULE property

func (icalModel IcalModel) daylight(tz *goics.Component) {
	dt := NewHtwkalenderComponent()
	dt.SetType("DAYLIGHT")
	dt.AddProperty("DTSTART", "19700329T020000")
	dt.AddProperty("TZOFFSETFROM", "+0100")
	dt.AddProperty("TZOFFSETTO", "+0200")
	dt.AddProperty("TZNAME", "CEST")
	dt.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU")
	tz.AddComponent(dt)
}

// if reminder is specified in the configuration for this event, an alarm will be added to the event
func addAlarmIfSpecified(s *goics.Component, event model.Event, mapping model.FeedCollection, clock clock.Clock) {
	// if event.Start > now
	// then add alarm
	if time.Time(event.Start).Local().After(clock.Now().Local()) && mapping.Reminder {
		a := goics.NewComponent()
		a.SetType("VALARM")
		a.AddProperty("TRIGGER", "-P0DT0H15M0S")
		a.AddProperty("ACTION", "DISPLAY")
		a.AddProperty("DESCRIPTION", "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms)
		s.AddComponent(a)
	}
}

// replaceNameIfUserDefined replaces the name of the event with the user defined name if it is not empty
// all contained template strings will be replaced with the corresponding values from the event
func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection) string {
	if !functions.OnlyWhitespace(mapping.UserDefinedName) {
		return names.ReplaceTemplateSubStrings(mapping.UserDefinedName, *event)
	}

	return event.Name
}

// AddPropertyIfNotEmpty adds a property to the component if the value is not empty
// or contains only whitespaces
func addPropertyIfNotEmpty(component *goics.Component, key string, value string) {
	if !functions.OnlyWhitespace(value) {
		component.AddProperty(key, value)
	}
}

func generateDescription(event model.Event) string {
	var description string

	if !functions.OnlyWhitespace(event.Prof) {
		description += "Profs: " + event.Prof + "\n"
	}
	if !functions.OnlyWhitespace(event.Course) {
		description += "Gruppen: " + event.Course + "\n"
	}
	if !functions.OnlyWhitespace(event.EventType) {
		description += "Typ: " + event.EventType + event.Compulsory + "\n"
	}
	if !functions.OnlyWhitespace(event.Notes) {
		description += "Notizen: " + event.Notes + "\n"
	}

	return description
}
