package rui

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

const (
	DateChangedEvent = "date-changed"
	DatePickerMin    = "date-picker-min"
	DatePickerMax    = "date-picker-max"
	DatePickerStep   = "date-picker-step"
	DatePickerValue  = "date-picker-value"
	dateFormat       = "2006-01-02"
)

// DatePicker - DatePicker view
type DatePicker interface {
	View
}

type datePickerData struct {
	viewData
	dateChangedListeners []func(DatePicker, time.Time)
}

// NewDatePicker create new DatePicker object and return it
func NewDatePicker(session Session, params Params) DatePicker {
	view := new(datePickerData)
	view.Init(session)
	setInitParams(view, params)
	return view
}

func newDatePicker(session Session) View {
	return NewDatePicker(session, nil)
}

func (picker *datePickerData) Init(session Session) {
	picker.viewData.Init(session)
	picker.tag = "DatePicker"
	picker.dateChangedListeners = []func(DatePicker, time.Time){}
}

func (picker *datePickerData) normalizeTag(tag string) string {
	tag = strings.ToLower(tag)
	switch tag {
	case Type, Min, Max, Step, Value:
		return "date-picker-" + tag
	}

	return tag
}

func (picker *datePickerData) Remove(tag string) {
	picker.remove(picker.normalizeTag(tag))
}

func (picker *datePickerData) remove(tag string) {
	switch tag {
	case DateChangedEvent:
		if len(picker.dateChangedListeners) > 0 {
			picker.dateChangedListeners = []func(DatePicker, time.Time){}
			picker.propertyChangedEvent(tag)
		}
		return

	case DatePickerMin:
		delete(picker.properties, DatePickerMin)
		if picker.created {
			removeProperty(picker.htmlID(), Min, picker.session)
		}

	case DatePickerMax:
		delete(picker.properties, DatePickerMax)
		if picker.created {
			removeProperty(picker.htmlID(), Max, picker.session)
		}

	case DatePickerStep:
		delete(picker.properties, DatePickerMax)
		if picker.created {
			removeProperty(picker.htmlID(), Step, picker.session)
		}

	case DatePickerValue:
		if _, ok := picker.properties[DatePickerValue]; ok {
			delete(picker.properties, DatePickerValue)
			date := GetDatePickerValue(picker, "")
			if picker.created {
				picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), date.Format(dateFormat)))
			}
			for _, listener := range picker.dateChangedListeners {
				listener(picker, date)
			}
		} else {
			return
		}

	default:
		picker.viewData.remove(tag)
		return
	}
	picker.propertyChangedEvent(tag)
}

func (picker *datePickerData) Set(tag string, value interface{}) bool {
	return picker.set(picker.normalizeTag(tag), value)
}

func (picker *datePickerData) set(tag string, value interface{}) bool {
	if value == nil {
		picker.remove(tag)
		return true
	}

	setTimeValue := func(tag string) (time.Time, bool) {
		switch value := value.(type) {
		case time.Time:
			picker.properties[tag] = value
			return value, true

		case string:
			if text, ok := picker.Session().resolveConstants(value); ok {
				if date, err := time.Parse(dateFormat, text); err == nil {
					picker.properties[tag] = value
					return date, true
				}
			}
		}

		notCompatibleType(tag, value)
		return time.Now(), false
	}

	switch tag {
	case DatePickerMin:
		old, oldOK := getDateProperty(picker, DatePickerMin, Min)
		if date, ok := setTimeValue(DatePickerMin); ok {
			if !oldOK || date != old {
				if picker.created {
					updateProperty(picker.htmlID(), Min, date.Format(dateFormat), picker.session)
				}
				picker.propertyChangedEvent(tag)
			}
			return true
		}

	case DatePickerMax:
		old, oldOK := getDateProperty(picker, DatePickerMax, Max)
		if date, ok := setTimeValue(DatePickerMax); ok {
			if !oldOK || date != old {
				if picker.created {
					updateProperty(picker.htmlID(), Max, date.Format(dateFormat), picker.session)
				}
				picker.propertyChangedEvent(tag)
			}
			return true
		}

	case DatePickerStep:
		oldStep := GetDatePickerStep(picker, "")
		if picker.setIntProperty(DatePickerStep, value) {
			if step := GetDatePickerStep(picker, ""); oldStep != step {
				if picker.created {
					if step > 0 {
						updateProperty(picker.htmlID(), Step, strconv.Itoa(step), picker.session)
					} else {
						removeProperty(picker.htmlID(), Step, picker.session)
					}
				}
				picker.propertyChangedEvent(tag)
			}
			return true
		}

	case DatePickerValue:
		oldDate := GetDatePickerValue(picker, "")
		if date, ok := setTimeValue(DatePickerMax); ok {
			if date != oldDate {
				if picker.created {
					picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), date.Format(dateFormat)))
				}
				for _, listener := range picker.dateChangedListeners {
					listener(picker, date)
				}
				picker.propertyChangedEvent(tag)
			}
			return true
		}

	case DateChangedEvent:
		switch value := value.(type) {
		case func(DatePicker, time.Time):
			picker.dateChangedListeners = []func(DatePicker, time.Time){value}

		case func(time.Time):
			fn := func(view DatePicker, date time.Time) {
				value(date)
			}
			picker.dateChangedListeners = []func(DatePicker, time.Time){fn}

		case []func(DatePicker, time.Time):
			picker.dateChangedListeners = value

		case []func(time.Time):
			listeners := make([]func(DatePicker, time.Time), len(value))
			for i, val := range value {
				if val == nil {
					notCompatibleType(tag, val)
					return false
				}

				listeners[i] = func(view DatePicker, date time.Time) {
					val(date)
				}
			}
			picker.dateChangedListeners = listeners

		case []interface{}:
			listeners := make([]func(DatePicker, time.Time), len(value))
			for i, val := range value {
				if val == nil {
					notCompatibleType(tag, val)
					return false
				}

				switch val := val.(type) {
				case func(DatePicker, time.Time):
					listeners[i] = val

				case func(time.Time):
					listeners[i] = func(view DatePicker, date time.Time) {
						val(date)
					}

				default:
					notCompatibleType(tag, val)
					return false
				}
			}
			picker.dateChangedListeners = listeners
		}
		picker.propertyChangedEvent(tag)
		return true

	default:
		return picker.viewData.set(tag, value)
	}
	return false
}

func (picker *datePickerData) Get(tag string) interface{} {
	return picker.get(picker.normalizeTag(tag))
}

func (picker *datePickerData) get(tag string) interface{} {
	switch tag {
	case DateChangedEvent:
		return picker.dateChangedListeners

	default:
		return picker.viewData.get(tag)
	}
}

func (picker *datePickerData) htmlTag() string {
	return "input"
}

func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) {
	picker.viewData.htmlProperties(self, buffer)

	buffer.WriteString(` type="date"`)

	if min, ok := getDateProperty(picker, DatePickerMin, Min); ok {
		buffer.WriteString(` min="`)
		buffer.WriteString(min.Format(dateFormat))
		buffer.WriteByte('"')
	}

	if max, ok := getDateProperty(picker, DatePickerMax, Max); ok {
		buffer.WriteString(` max="`)
		buffer.WriteString(max.Format(dateFormat))
		buffer.WriteByte('"')
	}

	if step, ok := intProperty(picker, DatePickerStep, picker.Session(), 0); ok && step > 0 {
		buffer.WriteString(` step="`)
		buffer.WriteString(strconv.Itoa(step))
		buffer.WriteByte('"')
	}

	buffer.WriteString(` value="`)
	buffer.WriteString(GetDatePickerValue(picker, "").Format(dateFormat))
	buffer.WriteByte('"')

	buffer.WriteString(` oninput="editViewInputEvent(this)"`)
}

func (picker *datePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
	if IsDisabled(self, "") {
		buffer.WriteString(` disabled`)
	}
	picker.viewData.htmlDisabledProperties(self, buffer)
}

func (picker *datePickerData) handleCommand(self View, command string, data DataObject) bool {
	switch command {
	case "textChanged":
		if text, ok := data.PropertyValue("text"); ok {
			if value, err := time.Parse(dateFormat, text); err == nil {
				oldValue := GetDatePickerValue(picker, "")
				picker.properties[DatePickerValue] = value
				if value != oldValue {
					for _, listener := range picker.dateChangedListeners {
						listener(picker, value)
					}
				}
			}
		}
		return true
	}

	return picker.viewData.handleCommand(self, command, data)
}

func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
	valueToTime := func(value interface{}) (time.Time, bool) {
		if value != nil {
			switch value := value.(type) {
			case time.Time:
				return value, true

			case string:
				if text, ok := view.Session().resolveConstants(value); ok {
					if result, err := time.Parse(dateFormat, text); err == nil {
						return result, true
					}
				}
			}
		}
		return time.Now(), false
	}

	if view != nil {
		if result, ok := valueToTime(view.getRaw(mainTag)); ok {
			return result, true
		}

		if value, ok := valueFromStyle(view, shortTag); ok {
			if result, ok := valueToTime(value); ok {
				return result, true
			}
		}
	}

	return time.Now(), false
}

// GetDatePickerMin returns the min date of DatePicker subview and "true" as the second value if the min date is set,
// "false" as the second value otherwise.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetDatePickerMin(view View, subviewID string) (time.Time, bool) {
	if subviewID != "" {
		view = ViewByID(view, subviewID)
	}
	if view != nil {
		return getDateProperty(view, DatePickerMin, Min)
	}
	return time.Now(), false
}

// GetDatePickerMax returns the max date of DatePicker subview and "true" as the second value if the min date is set,
// "false" as the second value otherwise.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetDatePickerMax(view View, subviewID string) (time.Time, bool) {
	if subviewID != "" {
		view = ViewByID(view, subviewID)
	}
	if view != nil {
		return getDateProperty(view, DatePickerMax, Max)
	}
	return time.Now(), false
}

// GetDatePickerStep returns the date changing step in days of DatePicker subview.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetDatePickerStep(view View, subviewID string) int {
	if subviewID != "" {
		view = ViewByID(view, subviewID)
	}
	if view != nil {
		if result, _ := intStyledProperty(view, DatePickerStep, 0); result >= 0 {
			return result
		}
	}
	return 0
}

// GetDatePickerValue returns the date of DatePicker subview.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetDatePickerValue(view View, subviewID string) time.Time {
	if subviewID != "" {
		view = ViewByID(view, subviewID)
	}
	if view == nil {
		return time.Now()
	}
	date, _ := getDateProperty(view, DatePickerValue, Value)
	return date
}

// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetDateChangedListeners(view View, subviewID string) []func(DatePicker, time.Time) {
	if subviewID != "" {
		view = ViewByID(view, subviewID)
	}
	if view != nil {
		if value := view.Get(DateChangedEvent); value != nil {
			if listeners, ok := value.([]func(DatePicker, time.Time)); ok {
				return listeners
			}
		}
	}
	return []func(DatePicker, time.Time){}
}