
// mateQuestionnaire provides functions to create and read mate questionnaires and questionnaire responses.
// Questionnaires are lists of questions that users can share on their profile.
// Users can filter matches based on their responses.
// Full questionnaire specification is provided in /documentation/Specification.md

package mateQuestionnaire

import "seekia/internal/allowedText"
import "seekia/internal/encoding"
import "seekia/internal/helpers"

import "strings"
import "errors"

type QuestionObject struct{

	// 9 or 10 byte hex encoded string
	// 9 bytes = Choice
	// 10 bytes = Entry
	Identifier string

	// "Choice"/"Entry"
	Type string

	// The content of the question
	// Example: "What is your job?"
	Content string

	Options string
}

//Will also verify questionnaire
func ReadQuestionnaireString(inputQuestionnaire string)([]QuestionObject, error){

	if (inputQuestionnaire == ""){
		return nil, errors.New("ReadQuestionnaireString called with empty questionnaire.")
	}

	questionsRawList := strings.Split(inputQuestionnaire, "+&")
	if (len(questionsRawList) > 25){
		return nil, errors.New("ReadQuestionnaireString called with questionnaire containing more than 25 questions.")
	}

	// We use this map to detect duplicates
	questionIdentifiersMap := make(map[string]struct{})	

	questionnaireQuestionsList := make([]QuestionObject, 0, len(questionsRawList))

	for _, rawQuestion := range questionsRawList{

		questionInfoList := strings.Split(rawQuestion, "%¢")

		if (len(questionInfoList) != 4){
			return nil, errors.New("Malformed questionnaire question: " + rawQuestion)
		}

		questionIdentifier := questionInfoList[0]
		questionType := questionInfoList[1]
		questionContent := questionInfoList[2]
		questionOptions := questionInfoList[3]

		getExpectedQuestionIdentifierLength := func()uint32{
			if (questionType == "Choice"){
				return 9
			}
			return 10
		}

		expectedQuestionIdentifierLength := getExpectedQuestionIdentifierLength()

		isValid := helpers.VerifyHexString(expectedQuestionIdentifierLength, questionIdentifier)
		if (isValid == false){
			return nil, errors.New("Malformed question: Invalid identifier: " + questionIdentifier)
		}

		_, exists := questionIdentifiersMap[questionIdentifier]
		if (exists == true){
			return nil, errors.New("Malformed questionnaire: Duplicate questionIdentifier exists.")
		}

		questionIdentifiersMap[questionIdentifier] = struct{}{}

		if (questionType != "Choice" && questionType != "Entry"){
			return nil, errors.New("Malformed question: Invalid question type: " + questionType)
		}

		if (questionContent == ""){
			return nil, errors.New("Malformed question: Empty content.")
		}

		isAllowed := allowedText.VerifyStringIsAllowed(questionContent)
		if (isAllowed == false){
			return nil, errors.New("Malformed question: Content contains unallowed text")
		}

		if (len(questionContent) > 500){
			return nil, errors.New("Malformed question: Content is too long.")
		}

		if (questionType == "Choice"){

			maxChoicesAllowed, choicesListString, delimiterFound := strings.Cut(questionOptions, "#")
			if (delimiterFound == false){
				return nil, errors.New("Malformed question: Invalid choice question options: Missing #")
			}

			maxChoicesAllowedInt, err := helpers.ConvertStringToInt(maxChoicesAllowed)
			if (err != nil) {
				return nil, errors.New("Malformed question: Invalid maximum choices allowed.")
			}
			if (maxChoicesAllowedInt < 1 || maxChoicesAllowedInt > 6){
				return nil, errors.New("Malformed question: Invalid max choices allowed")
			}

			choicesList := strings.Split(choicesListString, "$¥")
			if (len(choicesList) < 2 || len(choicesList) > 6){
				return nil, errors.New("Malformed question: Invalid choices list length.")
			}

			// We use this map to check for duplicates
			choicesMap := make(map[string]struct{})

			for _, choiceString := range choicesList{

				if (choiceString == ""){
					return nil, errors.New("Malformed question: Choice is empty.")
				}

				if (len(choiceString) > 100){
					return nil, errors.New("Malformed question: Choice text is too long.")
				}

				choiceIsAllowed := allowedText.VerifyStringIsAllowed(choiceString)			
				if (choiceIsAllowed == false){
					return nil, errors.New("Malformed question: Choice contains not-allowed text.")
				}

				_, exists := choicesMap[choiceString]
				if (exists == true){
					return nil, errors.New("Malformed question: Duplicate choice exists.")
				}
				choicesMap[choiceString] = struct{}{}
			}

		} else {
			// questionType == "Entry"

			if (questionOptions != "Numeric" && questionOptions != "Any"){
				return nil, errors.New("Malformed question: Invalid entry options.")		
			}
		}
		
		newQuestionObject := QuestionObject{
		
			Identifier: questionIdentifier,
			Type: questionType,
			Content: questionContent,
			Options: questionOptions,
		}

		questionnaireQuestionsList = append(questionnaireQuestionsList, newQuestionObject)
	}
	
	return questionnaireQuestionsList, nil
}

func CreateQuestionnaireString(inputQuestionnaireObject []QuestionObject)(string, error){

	if (len(inputQuestionnaireObject) == 0){
		return "", errors.New("CreateQuestionnaireString called with empty inputQuestionnaireObject.")
	}
	if (len(inputQuestionnaireObject) > 25){
		return "", errors.New("Cannot create questionnaire: More than 25 questions.")
	}

	questionsList := make([]string, 0, len(inputQuestionnaireObject))

	for _, questionObject := range inputQuestionnaireObject{
		
		questionIdentifier := questionObject.Identifier
		questionType := questionObject.Type
		questionContent := questionObject.Content
		questionOptions := questionObject.Options
	
		questionSlice := []string{questionIdentifier, questionType, questionContent, questionOptions}

		questionString := strings.Join(questionSlice, "%¢")

		questionsList = append(questionsList, questionString)
	}

	questionnaireString := strings.Join(questionsList, "+&")

	// Now we verify questionnaire:
	_, err := ReadQuestionnaireString(questionnaireString)
	if (err != nil){
		return "", errors.New("Cannot create questionnaire: Result questionnaire is invalid: " + err.Error())
	}

	return questionnaireString, nil
}

//Outputs:
//	-map[string]string: Response map (Question identifier -> Encoded question response)
//	-error
func ReadQuestionnaireResponse(inputResponseString string)(map[string]string, error){

	questionsList := strings.Split(inputResponseString, "+&")

	if (len(questionsList) > 25){
		return nil, errors.New("Invalid questionnaire response: More than 25 questions.")
	}

	responseMap := make(map[string]string)

	for _, element := range questionsList{
	
		questionIdentifier, questionResponse, delimiterFound := strings.Cut(element, "%")
		if (delimiterFound == false){
			return nil, errors.New("Invalid questionnaire response: Invalid encoded response")
		}

		questionIdentifierBytes, err := encoding.DecodeHexStringToBytes(questionIdentifier)
		if (err != nil){
			return nil, errors.New("Invalid questionnaire response: Question identifier is not hex: " + questionIdentifier)
		}

		if (len(questionIdentifierBytes) != 9 && len(questionIdentifierBytes) != 10){
			return nil, errors.New("Invalid questionnaire response: Question identifier is invalid length.")	
		}		

		if (len(questionIdentifierBytes) == 10){

			// Question is of type Entry

			if (questionResponse == ""){
				return nil, errors.New("Invalid questionnaire response: Entry response is empty.")
			}

			if (len(questionResponse) > 2000){
				return nil, errors.New("Invalid questionnaire response: Response too long.")
			}

		} else {

			// Question is of type Choice
			
			responseChoiceIndexesList := strings.Split(questionResponse, "$")
			if (len(responseChoiceIndexesList) > 6){
				return nil, errors.New("Invalid questionnaire response: Too many choices.")
			}

			// We use a map to prevent duplicates
			choicesMap := make(map[string]struct{})

			for _, responseChoiceIndex := range responseChoiceIndexesList{
			
				responseChoiceIndexInt, err := helpers.ConvertStringToInt(responseChoiceIndex)
				if (err != nil) {
					return nil, errors.New("Invalid questionnaire response: Choice index is not an integer.")
				}

				if (responseChoiceIndexInt < 1 || responseChoiceIndexInt > 6){
					return nil, errors.New("Invalid questionnaire response: Choice is an invalid int")
				}

				_, exists := choicesMap[responseChoiceIndex]
				if (exists == true){
					return nil, errors.New("Invalid questionnaire response: Duplicate choice exists.")
				}

				choicesMap[responseChoiceIndex] = struct{}{}
			}
		}

		responseMap[questionIdentifier] = questionResponse
	}

	return responseMap, nil
}

//Inputs:
//	-map[string]string: Response map (Question identifier -> Encoded question response)
//Outputs:
//	-string: Questionnaire response
//	-error
func CreateQuestionnaireResponse(inputResponseMap map[string]string)(string, error){

	questionsItemsList := make([]string, 0, len(inputResponseMap))

	for questionIdentifier, questionEncodedResponse := range inputResponseMap{

		encodedQuestionItem := questionIdentifier + "%" + questionEncodedResponse

		questionsItemsList = append(questionsItemsList, encodedQuestionItem)
	}

	questionnaireResponseString := strings.Join(questionsItemsList, "+&")

	_, err := ReadQuestionnaireResponse(questionnaireResponseString)
	if (err != nil) {
		return "", errors.New("Cannot create questionnaire response: Result is invalid: " + err.Error())
	}

	return questionnaireResponseString, nil
}



