import { Survey, SurveyChoice } from "../entities/Survey";
import { DatabaseHelper, JoinType } from "./DatabaseHelper";
import { NewSurvey } from "../entities/NewSurvey";
import { mysql_date } from "../utils/DateUtils";
import { SurveyResponse } from "../entities/SurveyResponse";

/**
 * Survey helper
 * 
 * @author Pierre HUBERT
 */

/**
 * Survey info table
 */
const SURVEY_INFO_TABLE = "sondage";

/**
 * Survey choices table
 */
const SURVEY_CHOICES_TABLE = "sondage_choix";

/**
 * Survey responses table
 */
const SURVEY_RESPONSE_TABLE = "sondage_reponse";

/**
 * Survey helper
 */
export class SurveyHelper {

	/**
	 * Create a new survey
	 * 
	 * @param survey Information about the survey to create
	 */
	public static async Create(survey: NewSurvey) {
		
		// Insert main row
		const surveyID = await DatabaseHelper.InsertRow(SURVEY_INFO_TABLE, {
			ID_utilisateurs: survey.userID,
			ID_texte: survey.postID,
			date_creation: mysql_date(),
			question: survey.question
		})

		// Process choices
		for(const choice of survey.choices) {
			await DatabaseHelper.InsertRow(SURVEY_CHOICES_TABLE, {
				ID_sondage: surveyID,
				date_creation: mysql_date(),
				Choix: choice
			});
		}
	}

	/**
	 * Check out whether a survey is associated to a post or not
	 * 
	 * @param postID Target post ID
	 */
	public static async Exists(postID: number) : Promise<boolean> {
		return await DatabaseHelper.Count({
			table: SURVEY_INFO_TABLE,
			where: {
				ID_texte: postID
			}
		}) > 0
	}

	/**
	 * Get the ID of the survey associated with a post
	 * 
	 * @param postID Target post ID
	 */
	public static async GetID(postID: number) : Promise<number> {
		const info = await DatabaseHelper.QueryRow({
			table: SURVEY_INFO_TABLE,
			where: {
				ID_texte: postID
			},
			fields: ["ID"]
		});

		if(info == null)
			throw new Error("Survey for post " + postID + " not found!");
		
		return info.ID;
	}

	/**
	 * Cancel the response of a user to a survey
	 * 
	 * @param userID Target user ID
	 * @param surveyID Target survey
	 */
	public static async CancelResponse(userID: number, surveyID: number) {
		await DatabaseHelper.DeleteRows(SURVEY_RESPONSE_TABLE, {
			ID_sondage: surveyID,
			ID_utilisateurs: userID
		});
	}

	/**
	 * Check out whether a choice for the survey exists or not
	 * 
	 * @param surveyID Target survey ID
	 * @param choiceID Target choice ID
	 */
	public static async ChoiceExists(surveyID: number, choiceID: number) : Promise<boolean> {
		return await DatabaseHelper.Count({
			table: SURVEY_CHOICES_TABLE,
			where: {
				ID_sondage: surveyID,
				ID: choiceID
			}
		}) > 0;
	}

	/**
	 * Save user response to a survey
	 * 
	 * @param userID Target user
	 * @param surveyID Target survey
	 * @param choiceID Selected choice
	 */
	public static async SendResponse(userID: number, surveyID: number, choiceID: number) {
		await DatabaseHelper.InsertRow(SURVEY_RESPONSE_TABLE, {
			ID_utilisateurs: userID,
			ID_sondage: surveyID,
			ID_sondage_choix: choiceID,
			date_envoi: mysql_date()
		});
	}

	/**
	 * Delete the survey associated to a post
	 * 
	 * @param postID Target post ID
	 */
	public static async Delete(postID: number) {
		const surveyID = await this.GetID(postID);
		
		DatabaseHelper.DeleteRows(SURVEY_RESPONSE_TABLE, {
			ID_sondage: surveyID
		});

		DatabaseHelper.DeleteRows(SURVEY_CHOICES_TABLE, {
			ID_sondage: surveyID
		});

		DatabaseHelper.DeleteRows(SURVEY_INFO_TABLE, {
			ID: surveyID
		});
	}

	/**
	 * Get information about the survey of a post
	 * 
	 * @param postID The ID of the associated post
	 */
	public static async GetInfo(postID: number) : Promise<Survey> {

		// Get main information about the survey
		const surveyRow = await DatabaseHelper.QueryRow({
			table: SURVEY_INFO_TABLE,
			where: {
				ID_texte: postID
			}
		});

		if(surveyRow == null)
			throw new Error("Could not find survey for post " + postID + " !");
		
		const survey = this.DBToSurveyInfo(surveyRow);


		// Get the choices of the survey
		const choices = await DatabaseHelper.Query({
			table: SURVEY_CHOICES_TABLE,
			tableAlias: "c",
			joinType: JoinType.LEFT,
			joins: [
				{
					table: SURVEY_RESPONSE_TABLE,
					tableAlias: "r",
					condition: "c.ID = r.ID_sondage_choix"
				}
			],
			where: {
				"c.ID_sondage": survey.id
			},
			groupBy: "c.ID",
			fields: ["c.*", "COUNT(r.ID) AS count_choice"]
		});

		choices.forEach((row) => survey.choices.push(this.DBToSurveyChoice(row)));

		return survey;
	}

	/**
	 * Get the choice of a user for a survey
	 * 
	 * @param surveyID Target survey
	 * @param userID Target user
	 */
	public static async GetUserChoice(surveyID: number, userID: number) {
		const result = await DatabaseHelper.QueryRow({
			table: SURVEY_RESPONSE_TABLE,
			where: {
				ID_utilisateurs: userID,
				ID_sondage: surveyID
			}
		});

		return result == null ? 0 /* no response yet */ : result.ID_sondage_choix;
	}

	/**
	 * Export all the responses to surveys of a user
	 * 
	 * @param userID The ID of the target user
	 */
	public static async ExportAllUserResponses(userID: number) : Promise<SurveyResponse[]> {
		return (await DatabaseHelper.Query({
			table: SURVEY_RESPONSE_TABLE,
			where: {
				ID_utilisateurs: userID
			}
		})).map(this.DBTosurveyResponse)
	}

	/**
	 * Delete all user responses to surveys
	 * 
	 * @param userID Target user ID
	 */
	public static async DeleteAllUserResponses(userID: number) {
		await DatabaseHelper.DeleteRows(SURVEY_RESPONSE_TABLE, {
			ID_utilisateurs: userID
		});
	}


	/**
	 * Turn a database entry into a survey object
	 * 
	 * @param row The row to transform
	 */
	private static DBToSurveyInfo(row: any) : Survey {
		return new Survey({
			id: row.ID,
			userID: row.ID_utilisateurs,
			postID: row.ID_texte,
			timeCreate: new Date(row.date_creation).getTime()/1000,
			question: row.question,
			choices: [],
			allowNewChoices: row.allow_new_choices == 1,
		});
	}

	/**
	 * Turn a database entry into a survey choice
	 * 
	 * @param row The row to transform
	 */
	private static DBToSurveyChoice(row: any) : SurveyChoice {
		return {
			id: row.ID,
			name: row.Choix,
			count: row.count_choice
		};
	}

	/**
	 * Turn a database entry into a survey response object
	 * 
	 * @param row The row
	 */
	private static DBTosurveyResponse(row: any) : SurveyResponse {
		return new SurveyResponse({
			id: row.ID,
			timeSent: new Date(row.date_envoi).getTime()/1000,
			userID: row.ID_utilisateurs,
			surveyID: row.ID_sondage,
			choiceID: row.ID_sondage_choix
		});
	}
}