package gui

// questionnaireGui.go implements pages to view and take a user's questionnaire
// Pages to build a user's questionnaire exist in buildProfileGui_General.go
// TODO: We need to add pages to view users who have taken a user's questionnaire, and statistics about those users and their responses

import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/widget"
import "fyne.io/fyne/v2/theme"
import "fyne.io/fyne/v2/container"
import "fyne.io/fyne/v2/layout"
import "fyne.io/fyne/v2/dialog"

import "seekia/internal/allowedText"
import "seekia/internal/helpers"
import "seekia/internal/mateQuestionnaire"
import "seekia/internal/myIdentity"

import "strings"
import "errors"

// This page is used to take a questionnaire
// Submit page is used to submit the completed questionnaire
//Inputs:
//	-fyne.Window
//	-[]mateQuestionnaire.QuestionObject: Input questionnaire
//	-int: Current viewed question index
//	-map[string]string: Current questionnaire response map to add responses to. Is submitted once questionnaire is completed.
//		-Structure: Question Identifier -> Response
//	-func(): Previous page
//	-func(response string, previousPage func()): Submit page
func setTakeQuestionnairePage(window fyne.Window, inputQuestionnaire []mateQuestionnaire.QuestionObject, currentIndex int, myResponsesMap map[string]string, previousPage func(), submitPage func(string, func())){

	currentPage := func(){setTakeQuestionnairePage(window, inputQuestionnaire, currentIndex, myResponsesMap, previousPage, submitPage)}

	title := getPageTitleCentered("Take Questionnaire")

	backButton := getBackButtonCentered(previousPage)

	if (len(inputQuestionnaire) == 0){
		setErrorEncounteredPage(window, errors.New("setTakeQuestionnairePage called with empty questionnaire."), previousPage)
		return
	}

	if (currentIndex < 0 || currentIndex > (len(inputQuestionnaire)-1) ){
		setErrorEncounteredPage(window, errors.New("setTakeQuestionnairePage called with invalid questionnaire index."), previousPage)
		return
	}

	getNavigateQuestionnaireButtons := func()(*fyne.Container, error){

		getNavigatePreviousButton := func()fyne.Widget{
			if (currentIndex == 0){
				emptyButton := widget.NewButton("", nil)
				return emptyButton
			}
			previousButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){
					setTakeQuestionnairePage(window, inputQuestionnaire, currentIndex-1, myResponsesMap, previousPage, submitPage)
				})
			return previousButton
		}

		navigatePreviousButton := getNavigatePreviousButton()
		navigateNextButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){

				if (currentIndex == len(inputQuestionnaire)-1){

					// No questions are left to view. We submit the questionnaire.

					if (len(myResponsesMap) == 0){
						dialogTitle := translate("No Questions Answered")
						dialogMessageA := getBoldLabelCentered(translate("You have not answered any questions."))
						dialogMessageB := getLabelCentered(translate("You must answer at least 1 question."))
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}

					newResponse, err := mateQuestionnaire.CreateQuestionnaireResponse(myResponsesMap)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					submitPage(newResponse, currentPage)
					return
				}
				setTakeQuestionnairePage(window, inputQuestionnaire, currentIndex+1, myResponsesMap, previousPage, submitPage)
			})

		navigateButtonsGrid := container.NewGridWithColumns(2, navigatePreviousButton, navigateNextButton)
		
		return navigateButtonsGrid, nil
	}
	
	navigateQuestionnaireButtons, err := getNavigateQuestionnaireButtons()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	currentQuestionViewIndex := helpers.ConvertIntToString(currentIndex + 1)
	totalQuestionsString := helpers.ConvertIntToString(len(inputQuestionnaire))

	currentIndexLabel := getBoldLabelCentered("Question " + currentQuestionViewIndex + " of " + totalQuestionsString)

	navigateQuestionnaireButtonsCentered := getContainerCentered(navigateQuestionnaireButtons)

	currentQuestionMap := inputQuestionnaire[currentIndex]

	currentQuestionContainer, err := getViewQuestionnaireQuestionContainer(window, currentPage, currentQuestionMap, myResponsesMap)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	
	page := container.NewVBox(title, backButton, widget.NewSeparator(), currentIndexLabel, navigateQuestionnaireButtonsCentered, widget.NewSeparator(), currentQuestionContainer)

	setPageContent(page, window)
}


func setSubmitQuestionnairePage(window fyne.Window, recipientIdentityHash [16]byte, questionnaireResponse string, previousPage func(), onCompletePage func()){

	currentPage := func(){setSubmitQuestionnairePage(window, recipientIdentityHash, questionnaireResponse, previousPage, onCompletePage)}

	title := getPageTitleCentered("Submit Questionnaire")

	backButton := getBackButtonCentered(previousPage)

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Mate")
	if (err != nil){
		setErrorEncounteredPage(window, err, currentPage)
		return
	}
	if (myIdentityExists == false){
		description1 := getBoldLabelCentered("Your Mate identity does not exist.")
		description2 := getLabelCentered("You must create it before sending your questionnaire response.")

		createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){
				setChooseNewIdentityHashPage(window, "Mate", currentPage, currentPage)			
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, createIdentityButton)
	
		setPageContent(page, window)
		return
	}

	description1 := getBoldLabelCentered("Are you sure you want to submit your questionnaire?")
	description2 := getLabelCentered("You will pay for the message on the next page.")

	myResponseMap, err := mateQuestionnaire.ReadQuestionnaireResponse(questionnaireResponse)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	numberOfResponses := len(myResponseMap)
	numberOfResponsesString := helpers.ConvertIntToString(numberOfResponses)

	numberOfResponsesLabel := widget.NewLabel("Number Of Responses:")
	numberOfResponsesText := getBoldLabel(numberOfResponsesString)

	numberOfResponsesRow := container.NewHBox(layout.NewSpacer(), numberOfResponsesLabel, numberOfResponsesText, layout.NewSpacer())

	messageCommunication := ">!>QuestionnaireResponse=" + questionnaireResponse

	confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){
			
			setFundAndSendChatMessagePage(window, myIdentityHash, recipientIdentityHash, messageCommunication, false, currentPage, onCompletePage)
		}))

	//TODO: After send, add to questionnaireHistory so we can keep track of our past answers and update them with new responses

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, numberOfResponsesRow, confirmButton)

	setPageContent(page, window)
}

// This function returns a container containing a questionnaire choice/entry question with a submit button
// It is used on the question preview page and when a user is taking a questionnaire
func getViewQuestionnaireQuestionContainer(window fyne.Window, currentPage func(), questionObject mateQuestionnaire.QuestionObject, myResponsesMap map[string]string)(*fyne.Container, error){

	questionIdentifier := questionObject.Identifier
	questionType := questionObject.Type
	questionContent := questionObject.Content
	questionOptions := questionObject.Options

	questionLabel := getBoldLabelCentered("Question:")

	questionContentTrimmed, _, err := helpers.TrimAndFlattenString(questionContent, 30)
	if (err != nil) { return nil, err }
	
	questionContentLabel := getLabelCentered(questionContentTrimmed)

	viewQuestionContentButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewTextPage(window, "Viewing Question", questionContent, false, currentPage)
		})

	questionContentRow := container.NewHBox(layout.NewSpacer(), questionContentLabel, viewQuestionContentButton, layout.NewSpacer())

	if (questionType == "Choice"){

		//TODO: Deal with long choices

		maximumAnswersAllowedString, choicesListString, delimiterFound := strings.Cut(questionOptions, "#")
		if (delimiterFound == false){
			return nil, errors.New("Malformed question object: Invalid choice question options: " + questionOptions)
		}
	
		maximumAnswersAllowedInt, err := helpers.ConvertStringToInt(maximumAnswersAllowedString)
		if (err != nil) { return nil, err }
		if (maximumAnswersAllowedInt < 1 || maximumAnswersAllowedInt > 6){
			return nil, errors.New("Malformed question object: Invalid maximum answers allowed: " + maximumAnswersAllowedString)
		}

		choicesList := strings.Split(choicesListString, "$¥")

		if (len(choicesList) < 2 || len(choicesList) > 6){
			return nil, errors.New("Malformed question object: Invalid choices.")
		}

		getMaximumAnswersAllowedAdjusted := func()string{

			if (maximumAnswersAllowedInt > len(choicesList)){
				maximumAnswersAllowedAdjusted := helpers.ConvertIntToString(len(choicesList))

				return maximumAnswersAllowedAdjusted
			}

			return maximumAnswersAllowedString
		}

		maximumAnswersAllowedAdjusted := getMaximumAnswersAllowedAdjusted()

		getChooseChoiceLabelText := func()string{
			if (maximumAnswersAllowedInt == 1){
				return "Select Answer:"
			}
			return "Select Answer(s):"
		}
		chooseChoiceLabelText := getChooseChoiceLabelText()
		chooseChoiceLabel := getBoldLabelCentered(chooseChoiceLabelText)
		maximumAnswersLabel := getWidgetCentered(getItalicLabel("Maximum: " + maximumAnswersAllowedAdjusted))

		getChoiceSelectButtons := func()(fyne.Widget, error){

			if (maximumAnswersAllowedInt == 1){

				questionSelector := widget.NewRadioGroup(choicesList, func(newChoice string){

						if (newChoice == ""){
							delete(myResponsesMap, questionIdentifier)
							return
						}

						getNewSelectedIndex := func()(int, error){

							for index, element := range choicesList{

								if (element == newChoice){
									return index, nil
								}
							}
							return 0, errors.New("questionSelector onChanged function called with unknown choice: " + newChoice)
						}

						newSelectedIndex, err := getNewSelectedIndex()
						if (err != nil){
							setErrorEncounteredPage(window, err, currentPage)
							return
						}

						// Encoded response is index+1
						// For example, if we chose the first option in the list, our encoded response will contain "1"

						newEncodedResponse := helpers.ConvertIntToString(newSelectedIndex+1)

						myResponsesMap[questionIdentifier] = newEncodedResponse
					})

				myEncodedResponse, myResponseExists := myResponsesMap[questionIdentifier]
				if (myResponseExists == true){

					mySelectedOptionInt, err := helpers.ConvertStringToInt(myEncodedResponse)
					if (err != nil){
						return nil, errors.New("myResponsesMap is malformed: Contains invalid 1-selection-allowed choice question response: " + myEncodedResponse)
					}

					mySelectedOptionIndex := mySelectedOptionInt-1

					if (mySelectedOptionIndex < 0 || mySelectedOptionIndex > (len(choicesList) - 1)){
						return nil, errors.New("myResponsesMap is malformed: Contains invalid 1-selection-allowed choice question response: Item index is out of range: " + myEncodedResponse)
					}

					mySelectedOption := choicesList[mySelectedOptionIndex]

					questionSelector.Selected = mySelectedOption
				}

				return questionSelector, nil
			}

			getMySelectionsList := func()([]string, error){

				myEncodedResponse, myResponseExists := myResponsesMap[questionIdentifier]
				if (myResponseExists == false){
					emptyList := make([]string, 0)
					return emptyList, nil
				}

				mySelectedIndexesList := strings.Split(myEncodedResponse, "$")

				mySelectionsList := make([]string, 0, len(mySelectedIndexesList))

				for _, mySelectionIndexString := range mySelectedIndexesList{

					mySelectionIndexInt, err := helpers.ConvertStringToInt(mySelectionIndexString)
					if (err != nil) {
						return nil, errors.New("myResponsesMap is malformed: Choice response contains non-int item: " + mySelectionIndexString)
					}

					indexInt := mySelectionIndexInt - 1

					if (indexInt < 0 || indexInt > (len(choicesList)-1)){
						return nil, errors.New("myResponsesMap is malformed: MySelectionIndex is out of range.")
					}

					mySelectedItem := choicesList[indexInt]
					mySelectionsList = append(mySelectionsList, mySelectedItem)
				}

				return mySelectionsList, nil
			}

			mySelectionsList, err := getMySelectionsList()
			if (err != nil) { return nil, err }

			choicesButtons := widget.NewCheckGroup(choicesList, nil)

			handleChoiceSelectionFunction := func(newSelectionsList []string){

				if (len(newSelectionsList) == 0){
					delete(myResponsesMap, questionIdentifier)
					mySelectionsList = make([]string, 0)
					return
				}

				if (len(newSelectionsList) > maximumAnswersAllowedInt){

					// We undo this selection
					choicesButtons.Selected = mySelectionsList

					dialogTitle := translate("Too Many Selections")
					dialogMessageA := getLabelCentered(translate("You have selected too many responses."))
					dialogMessageB := getLabelCentered(translate("You can only select " + maximumAnswersAllowedString + " responses."))
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
					dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
					return
				}

				encodedSelectionsList := make([]string, 0)

				for _, selectedChoice := range newSelectionsList{

					getSelectedChoiceIndex := func()(int, error){

						for index, element := range choicesList{
							if (element == selectedChoice){
								return index, nil
							}
						}

						return 0, errors.New("Selected choice not found in choices list.")
					}

					selectedChoiceIndex, err := getSelectedChoiceIndex()
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}

					encodedSelection := helpers.ConvertIntToString(selectedChoiceIndex + 1)

					encodedSelectionsList = append(encodedSelectionsList, encodedSelection)
				}

				encodedResponse := strings.Join(encodedSelectionsList, "$")
				myResponsesMap[questionIdentifier] = encodedResponse

				mySelectionsList = newSelectionsList
			}

			choicesButtons.OnChanged = handleChoiceSelectionFunction

			choicesButtons.Selected = mySelectionsList

			return choicesButtons, nil
		}

		choiceSelectButtons, err := getChoiceSelectButtons()
		if (err != nil) { return nil, err }

		choicesButtonsCentered := getWidgetCentered(choiceSelectButtons)

		noResponseButton := getWidgetCentered(widget.NewButtonWithIcon("No Response", theme.CancelIcon(), func(){

				_, exists := myResponsesMap[questionIdentifier]
				if (exists == false){
					// No response exists
					return
				}

				delete(myResponsesMap, questionIdentifier)
				currentPage()
			}))

		questionContainer := container.NewVBox(questionLabel, questionContentRow, widget.NewSeparator(), chooseChoiceLabel, maximumAnswersLabel, choicesButtonsCentered, noResponseButton)
		
		return questionContainer, nil
	}

	if (questionType != "Entry"){
		return nil, errors.New("getViewQuestionnaireQuestionContainer called with invalid questionObject: Invalid question type: " + questionType)
	}
	if (questionOptions != "Numeric" && questionOptions != "Any"){
		return nil, errors.New("getViewQuestionnaireQuestionContainer called with invalid questionObject: Invalid Entry question options: " + questionOptions)
	}

	enterResponseLabel := getBoldLabelCentered("Enter " + questionOptions + " Response:")

	responseEntry := widget.NewMultiLineEntry()
	responseEntry.Wrapping = 3

	myResponse, myResponseExists := myResponsesMap[questionIdentifier]
	if (myResponseExists == true){
		responseEntry.TextStyle = getFyneTextStyle_Bold()
		responseEntry.SetText(myResponse)
	} else {
		responseEntry.SetPlaceHolder("Enter response...")
	}

	responseEntry.OnChanged = func(newResponse string){

		if (myResponseExists == true && newResponse == myResponse){
			responseEntry.TextStyle = getFyneTextStyle_Bold()
		} else {
			responseEntry.TextStyle = getFyneTextStyle_Standard()
		}
	}

	responseEntryBoxed := getWidgetBoxed(responseEntry)

	submitButton := getWidgetCentered(widget.NewButtonWithIcon("Submit", theme.ConfirmIcon(), func(){

			newResponse := responseEntry.Text

			if (newResponse == ""){
				delete(myResponsesMap, questionIdentifier)
				currentPage()
				return
			}

			if (len(newResponse) > 2000){

				currentResponseCharacterCountString := helpers.ConvertIntToString(len(newResponse))

				title := translate("Response Is Too Long.")
				dialogMessageA := getLabelCentered("The longest response allowed is 2000 bytes.")
				dialogMessageB := getLabelCentered("Your response bytes count: " + currentResponseCharacterCountString)
				dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
				dialog.ShowCustom(title, translate("Close"), dialogContent, window)
				return
			}

			if (questionOptions == "Numeric"){
				responseIsNumeric := helpers.VerifyStringIsFloat(newResponse)
				if (responseIsNumeric == false){
					title := translate("Response Is Not Numeric.")
					dialogMessageA := getLabelCentered("You must respond with a number.")
					dialogMessageB := getLabelCentered("Change your response to a number.")
					dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
					dialog.ShowCustom(title, translate("Close"), dialogContent, window)
					return
				}
			}

			responseIsAllowed := allowedText.VerifyStringIsAllowed(newResponse)
			if (responseIsAllowed == false){
				dialogTitle := translate("Response Is Invalid.")
				dialogMessageA := getLabelCentered(translate("Response contains a prohibited character."))
				dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8."))
				dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}

			isContained := strings.Contains(newResponse, "+&")
			if (isContained == true){
				dialogTitle := translate("Response Is Invalid.")
				dialogMessageA := getLabelCentered(translate("Question contains prohibited string: ") + "+&")
				dialogMessageB := getLabelCentered(translate("Remove this string and resubmit."))
				dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}

			myResponsesMap[questionIdentifier] = newResponse	
			currentPage()
		}))

	noResponseButton := getWidgetCentered(widget.NewButtonWithIcon("No Response", theme.CancelIcon(), func(){
			delete(myResponsesMap, questionIdentifier)
			currentPage()
		}))

	widener := widget.NewLabel("                                                               ")
	heightener := widget.NewLabel("")

	buttonsWithWidener := container.NewVBox(submitButton, noResponseButton, widener, heightener)

	entryWithButtons := getContainerCentered(container.NewGridWithColumns(1, responseEntryBoxed, buttonsWithWidener))

	questionContainer := container.NewVBox(questionLabel, questionContentRow, widget.NewSeparator(), enterResponseLabel, entryWithButtons)
	
	return questionContainer, nil
}


