// swad_alert.c: alerts

/*
    SWAD (Shared Workspace At a Distance),
    is a web platform developed at the University of Granada (Spain),
    and used to support university teaching.

    This file is part of SWAD core.
    Copyright (C) 1999-2025 Antonio Caas Vargas

    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 <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/********************************* Headers ***********************************/
/*****************************************************************************/

#define _GNU_SOURCE 		// For vasprintf
#include <stdarg.h>		// For va_start, va_end
#include <stddef.h>		// For NULL
#include <stdio.h>		// For FILE, vasprintf
#include <stdlib.h>		// For free
#include <string.h>		// For string functions

#include "swad_action_list.h"
#include "swad_alert.h"
#include "swad_box.h"
#include "swad_error.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_HTML.h"
// #include "swad_show.h"

/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/

extern struct Globals Gbl;

/*****************************************************************************/
/************************* Private global variables **************************/
/*****************************************************************************/

static struct
  {
   size_t Num;		// Number of alert
   struct
     {
      Ale_AlertType_t Type;
      char *Text;	// Message to be displayed
      char *Section;	// Where to display the alert
     } List[Ale_MAX_ALERTS];
  } Ale_Alerts =	// Alert message created in a function and printed in a subsequent function
  {
   .Num = 0,		// No pending alerts to be shown
  };

/*****************************************************************************/
/***************************** Private prototypes ****************************/
/*****************************************************************************/

static void Ale_ResetLastAlert (void);
static void Ale_ResetAlert (size_t NumAlert);

static void Ale_ShowFixAlert (Ale_AlertType_t AlertType,const char *Txt);

static void Ale_ShowFixAlertAndButtonBegin (Ale_AlertType_t AlertType,const char *Txt);

/*****************************************************************************/
/**************************** Create a new alert *****************************/
/*****************************************************************************/

void Ale_CreateAlert (Ale_AlertType_t Type,const char *Section,
                      const char *fmt,...)
  {
   va_list ap;
   int NumBytesPrinted;
   size_t i;

   if (Ale_Alerts.Num + 1 > Ale_MAX_ALERTS)
      Err_ShowErrorAndExit ("Too many alerts.");

   i = Ale_Alerts.Num;
   Ale_Alerts.Num++;

   Ale_Alerts.List[i].Type = Type;

   Ale_Alerts.List[i].Section = NULL;
   if (Section)
      if (Section[0])
	 if (asprintf (&Ale_Alerts.List[i].Section,"%s",Section) < 0)
	    Err_NotEnoughMemoryExit ();

   va_start (ap,fmt);
   NumBytesPrinted = vasprintf (&Ale_Alerts.List[i].Text,fmt,ap);
   va_end (ap);
   if (NumBytesPrinted < 0)	// -1 if no memory or any other error
      Err_NotEnoughMemoryExit ();
  }

/*****************************************************************************/
/***************** Get current number of delayed alerts **********************/
/*****************************************************************************/

size_t Ale_GetNumAlerts (void)
  {
   return Ale_Alerts.Num;
  }

/*****************************************************************************/
/********************** Get type of last delayed alert ***********************/
/*****************************************************************************/

Ale_AlertType_t Ale_GetTypeOfLastAlert (void)
  {
   return Ale_Alerts.Num ? Ale_Alerts.List[Ale_Alerts.Num - 1].Type :
			   Ale_NONE;
  }

/*****************************************************************************/
/********************** Get text of last delayed alert ***********************/
/*****************************************************************************/

const char *Ale_GetTextOfLastAlert (void)
  {
   return Ale_Alerts.Num ? Ale_Alerts.List[Ale_Alerts.Num - 1].Text :
			   NULL;
  }

/*****************************************************************************/
/***************************** Reset all alerts ******************************/
/*****************************************************************************/

void Ale_ResetAllAlerts (void)
  {
   size_t NumAlert;

   for (NumAlert = 0;
	NumAlert < Ale_Alerts.Num;
	NumAlert++)
      Ale_ResetAlert (NumAlert);
  }

/*****************************************************************************/
/************************* Reset more recent alert ***************************/
/*****************************************************************************/

static void Ale_ResetLastAlert (void)
  {
   if (Ale_Alerts.Num)	// There are pending alerts not shown
      Ale_ResetAlert (Ale_Alerts.Num - 1);	// Reset the last one
  }

/*****************************************************************************/
/********************* Reset one alert given its index ***********************/
/*****************************************************************************/

static void Ale_ResetAlert (size_t NumAlert)
  {
   bool NoMoreAlertsPending;
   size_t i;

   if (NumAlert < Ale_Alerts.Num)
      if (Ale_Alerts.List[NumAlert].Type != Ale_NONE)
	{
	 /***** Reset alert *****/
	 Ale_Alerts.List[NumAlert].Type = Ale_NONE;	// Reset alert

	 /***** Free memory allocated for text *****/
	 if (Ale_Alerts.List[NumAlert].Text)
	   {
	    free (Ale_Alerts.List[NumAlert].Text);
	    Ale_Alerts.List[NumAlert].Text = NULL;
	   }

	 /***** Free memory allocated for section *****/
	 if (Ale_Alerts.List[NumAlert].Section)
	   {
	    free (Ale_Alerts.List[NumAlert].Section);
	    Ale_Alerts.List[NumAlert].Section = NULL;
	   }
	}

   /***** Set number of alerts to 0
          if there are no more alerts
          pending to be shown *****/
   NoMoreAlertsPending = true;
   for (i = 0;
	NoMoreAlertsPending && i < Ale_Alerts.Num;
	i++)
      if (Ale_Alerts.List[i].Type != Ale_NONE)
	 NoMoreAlertsPending = false;

   if (NoMoreAlertsPending)
      Ale_Alerts.Num = 0;
  }

/*****************************************************************************/
/************************ Show alert messages and exit ***********************/
/*****************************************************************************/

void Ale_ShowAlertsAndExit ()
  {
   Ale_ShowAlerts (NULL);
   Err_ShowErrorAndExit (NULL);
  }

/*****************************************************************************/
/****** Show several alert messages stored in vector of delayed alerts *******/
/*****************************************************************************/
// If Section == NULL ==> show all alerts
// If Section != NULL ==> shown only the alerts assigned to Section

void Ale_ShowAlerts (const char *Section)
  {
   size_t NumAlerts = Ale_GetNumAlerts ();
   size_t NumAlert;
   Lay_Show_t ShowAlert;

   for (NumAlert = 0;
	NumAlert < NumAlerts;
	NumAlert++)
      if (Ale_Alerts.List[NumAlert].Type != Ale_NONE)
        {
	 if (Section)
	    ShowAlert = strcmp (Ale_Alerts.List[NumAlert].Section,Section) ? Lay_DONT_SHOW :
									     Lay_SHOW;
	 else
	    ShowAlert = Lay_SHOW;

	 if (ShowAlert == Lay_SHOW)
	   {
	    Ale_ShowFixAlert (Ale_Alerts.List[NumAlert].Type,
			      Ale_Alerts.List[NumAlert].Text);
	    Ale_ResetAlert (NumAlert);
	   }
        }
  }

/*****************************************************************************/
/****************** Show one formatted-text alert message ********************/
/*****************************************************************************/

void Ale_ShowAlert (Ale_AlertType_t AlertType,const char *fmt,...)
  {
   va_list ap;
   int NumBytesPrinted;
   char *Txt;

   if (AlertType != Ale_NONE)
     {
      /***** Print format and list of variables into text *****/
      va_start (ap,fmt);
      NumBytesPrinted = vasprintf (&Txt,fmt,ap);
      va_end (ap);
      if (NumBytesPrinted < 0)	// -1 if no memory or any other error
	 Err_NotEnoughMemoryExit ();

      /***** Show alert *****/
      Ale_ShowFixAlert (AlertType,Txt);

      /***** Free text *****/
      free (Txt);
     }
  }

/*****************************************************************************/
/********************** Show one fix-text alert message **********************/
/*****************************************************************************/

static void Ale_ShowFixAlert (Ale_AlertType_t AlertType,const char *Txt)
  {
   if (AlertType != Ale_NONE)
     {
      /****** Print fix alert and button ******/
      Ale_ShowFixAlertAndButtonBegin (AlertType,Txt);
      Ale_ShowAlertAndButtonEnd (ActUnk,NULL,NULL,NULL,NULL,Btn_NO_BUTTON);
     }
  }

/*****************************************************************************/
/**************** Show the more recent alert with a button *******************/
/*****************************************************************************/

void Ale_ShowLastAlertAndButtonGo (Act_Action_t NextAction,
                                   void (*FuncPars) (void *Args),void *Args,
				   const char *Where)
  {
   extern const char *Txt_Go_to_X_QUESTION;
   char *Title;

   /***** Show last alert and then reset it *****/
   Ale_ShowLastAlertAndButtonBegin ();

   /***** Show question "Go to ... ?" *****/
   HTM_DIV_Begin ("class=\"CM ALERT_TXT_%s\"",The_GetSuffix ());
      if (asprintf (&Title,Txt_Go_to_X_QUESTION,Where) < 0)
	 Err_NotEnoughMemoryExit ();
      HTM_Txt (Title);
      free (Title);
   HTM_DIV_End ();

   /***** Show button *****/
   Ale_ShowAlertAndButtonEnd (NextAction,NULL,NULL,FuncPars,Args,Btn_GO);
  }

/*****************************************************************************/
/********** Show the first part of more recent alert with a button ***********/
/*****************************************************************************/

void Ale_ShowLastAlertAndButtonBegin (void)
  {
   /***** Show last alert *****/
   Ale_ShowFixAlertAndButtonBegin (Ale_GetTypeOfLastAlert (),
				   Ale_GetTextOfLastAlert ());

   /***** Reset last alert *****/
   Ale_ResetLastAlert ();
  }

/*****************************************************************************/
/************** Show an alert with a button to confirm removal ***************/
/*****************************************************************************/

void Ale_ShowAlertRemove (Act_Action_t NextAction,const char *Anchor,
                          void (*FuncPars) (void *Args),void *Args,
			  const char *Txt,const char *Title)
  {
   Ale_ShowAlertAndButton (NextAction,Anchor,NULL,
                           FuncPars,Args,
			   Btn_REMOVE,
			   Ale_QUESTION,Txt,Title);
  }

/*****************************************************************************/
/*********************** Show an alert with a button *************************/
/*****************************************************************************/

void Ale_ShowAlertAndButton (Act_Action_t NextAction,const char *Anchor,const char *OnSubmit,
                             void (*FuncPars) (void *Args),void *Args,
                             Btn_Button_t Button,
			     Ale_AlertType_t AlertType,const char *fmt,...)
  {
   va_list ap;
   int NumBytesPrinted;
   char *Txt = NULL;

   /***** Print format and list of variables into text *****/
   if (fmt)
      if (fmt[0])
	{
	 va_start (ap,fmt);
	 NumBytesPrinted = vasprintf (&Txt,fmt,ap);
	 va_end (ap);
	 if (NumBytesPrinted < 0)	// -1 if no memory or any other error
	    Err_NotEnoughMemoryExit ();
	}

   /****** Print fix alert and button ******/
   Ale_ShowFixAlertAndButtonBegin (AlertType,Txt);
   Ale_ShowAlertAndButtonEnd (NextAction,Anchor,OnSubmit,FuncPars,Args,Button);

   /***** Free text *****/
   if (Txt)
      free (Txt);
  }

/*****************************************************************************/
/******** Show the first part of a formatted-text alert with a button ********/
/*****************************************************************************/

void Ale_ShowAlertAndButtonBegin (Ale_AlertType_t AlertType,const char *fmt,...)
  {
   va_list ap;
   int NumBytesPrinted;
   char *Txt = NULL;

   /***** Print format and list of variables into text *****/
   if (fmt)
      if (fmt[0])
	{
	 va_start (ap,fmt);
	 NumBytesPrinted = vasprintf (&Txt,fmt,ap);
	 va_end (ap);
	 if (NumBytesPrinted < 0)	// -1 if no memory or any other error
	    Err_NotEnoughMemoryExit ();
	}

   /****** Print start of fix alert and button ******/
   Ale_ShowFixAlertAndButtonBegin (AlertType,Txt);

   /***** Free text *****/
   if (Txt)
      free (Txt);
  }

/*****************************************************************************/
/*********** Show the first part of a fix-text alert with a button ***********/
/*****************************************************************************/

static void Ale_ShowFixAlertAndButtonBegin (Ale_AlertType_t AlertType,const char *Txt)
  {
   extern const char *Txt_Close;
   char IdAlert[Frm_MAX_BYTES_ID + 1];
   static Box_Closable_t AlertClosable[Ale_NUM_ALERT_TYPES] =
     {
      [Ale_NONE     ] = Box_NOT_CLOSABLE,
      [Ale_CLIPBOARD] = Box_CLOSABLE,
      [Ale_INFO     ] = Box_CLOSABLE,
      [Ale_SUCCESS  ] = Box_CLOSABLE,
      [Ale_QUESTION ] = Box_CLOSABLE,
      [Ale_WARNING  ] = Box_CLOSABLE,
      [Ale_ERROR    ] = Box_CLOSABLE,
      [Ale_DEBUG    ] = Box_CLOSABLE,
    };
   static const char *Ale_AlertIcons[Ale_NUM_ALERT_TYPES] =
     {
      [Ale_NONE     ] = NULL,
      [Ale_CLIPBOARD] = "clipboard.svg",
      [Ale_INFO     ] = "info-circle-blue.svg",
      [Ale_SUCCESS  ] = "check-circle-green.svg",
      [Ale_QUESTION ] = "question64x64.gif",	// animated gif
      [Ale_WARNING  ] = "warning64x64.gif",	// animated gif
      [Ale_ERROR    ] = "error64x64.gif",	// animated gif
      [Ale_DEBUG    ] = "warning64x64.gif",	// animated gif
     };

   /****** If start of page is not written yet, do it now ******/
   if (!Gbl.Layout.HTMLStartWritten)
      Lay_WriteStartOfPage ();

   /***** Begin container *****/
   switch (AlertClosable[AlertType])
     {
      case Box_NOT_CLOSABLE:
         HTM_DIV_Begin ("class=\"CM\"");
	 break;
      case Box_CLOSABLE:
	 /* Create unique id for alert */
	 Frm_SetUniqueId (IdAlert);
	 HTM_DIV_Begin ("id=\"%s\" class=\"CM\"",IdAlert);
	 break;
     }

   /***** Begin box *****/
   HTM_DIV_Begin ("class=\"ALERT ALERT_BG_%s\"",The_GetSuffix ());

      /***** Icon to close the alert *****/
      switch (AlertClosable[AlertType])
	{
	 case Box_NOT_CLOSABLE:
	    break;
	 case Box_CLOSABLE:
	    HTM_DIV_Begin ("class=\"ALERT_CLOSE\"");
	       HTM_A_Begin ("href=\"\" onclick=\"toggleDisplay('%s');return false;\" /",
			    IdAlert);
		  Ico_PutIcon ("times.svg",Ico_BLACK,Txt_Close,"ICO16x16");
	       HTM_A_End ();
	    HTM_DIV_End ();
	    break;
	}

      /***** Write message *****/
      switch (AlertType)
        {
         case Ale_NONE:
	    HTM_DIV_Begin ("class=\"ALERT_TXT ALERT_TXT_%s\"",The_GetSuffix ());
	    break;
         default:
	    HTM_DIV_Begin ("class=\"ALERT_TXT ALERT_TXT_%s\""
			   " style=\"background-image:url('%s/%s');\"",
			   The_GetSuffix (),
			   Cfg_URL_ICON_PUBLIC,Ale_AlertIcons[AlertType]);
	    break;
        }
      HTM_Txt (Txt);
      HTM_DIV_End ();
  }

/*****************************************************************************/
/*************** Show the second part of an alert with a button **************/
/*****************************************************************************/

void Ale_ShowAlertAndButtonEnd (Act_Action_t NextAction,const char *Anchor,const char *OnSubmit,
                                void (*FuncPars) (void *Args),void *Args,
                                Btn_Button_t Button)
  {
      /***** Optional button *****/
      if (NextAction != ActUnk && Button != Btn_NO_BUTTON)
	{
	 /* Begin form */
	 Frm_BeginFormAnchorOnSubmit (NextAction,Anchor,OnSubmit);
	    if (FuncPars)
	       FuncPars (Args);

	    /* Put button */
	    Btn_PutButton (Button,NULL);

	 /* End form */
	 Frm_EndForm ();
	}

      /***** End box *****/
      HTM_DIV_End ();

   /***** End container *****/
   HTM_DIV_End ();
  }

/*****************************************************************************/
/**************** Create alert when a form field is empty ********************/
/*****************************************************************************/

void Ale_ShowAlertYouCanNotLeaveFieldEmpty (void)
  {
   extern const char *Txt_You_can_not_leave_the_field_empty;

   Ale_ShowAlert (Ale_WARNING,Txt_You_can_not_leave_the_field_empty);
  }

/*****************************************************************************/
/*** Show alert when user not found or no permission to perform an action ****/
/*****************************************************************************/

void Ale_ShowAlertUserNotFoundOrYouDoNotHavePermission (void)
  {
   extern const char *Txt_User_not_found_or_you_do_not_have_permission_;

   Ale_ShowAlert (Ale_WARNING,Txt_User_not_found_or_you_do_not_have_permission_);
  }

/*****************************************************************************/
/** Create alert when user not found or no permission to perform an action ***/
/*****************************************************************************/

void Ale_CreateAlertUserNotFoundOrYouDoNotHavePermission (void)
  {
   extern const char *Txt_User_not_found_or_you_do_not_have_permission_;

   Ale_CreateAlert (Ale_WARNING,NULL,
		    Txt_User_not_found_or_you_do_not_have_permission_);
  }

/*****************************************************************************/
/**************** Create alert when a form field is empty ********************/
/*****************************************************************************/

void Ale_CreateAlertYouCanNotLeaveFieldEmpty (void)
  {
   extern const char *Txt_You_can_not_leave_the_field_empty;

   Ale_CreateAlert (Ale_WARNING,NULL,Txt_You_can_not_leave_the_field_empty);
  }

/*****************************************************************************/
/********************* Create alert when title is empty **********************/
/*****************************************************************************/

void Ale_CreateAlertYouMustSpecifyTheTitle (void)
  {
   extern const char *Txt_You_must_specify_the_title;

   Ale_CreateAlert (Ale_WARNING,NULL,
		    Txt_You_must_specify_the_title);
  }

/*****************************************************************************/
/****************** Create alert when web address is empty *******************/
/*****************************************************************************/

void Ale_CreateAlertYouMustSpecifyTheWebAddress (void)
  {
   extern const char *Txt_You_must_specify_the_web_address;

   Ale_CreateAlert (Ale_WARNING,NULL,
		    Txt_You_must_specify_the_web_address);
  }

/*****************************************************************************/
/************ Create alert when short name or full name are empty ************/
/*****************************************************************************/

void Ale_CreateAlertYouMustSpecifyTheName (void)
  {
   extern const char *Txt_You_must_specify_the_name;

   Ale_CreateAlert (Ale_WARNING,NULL,
		    Txt_You_must_specify_the_name);
  }

/*****************************************************************************/
/************ Create alert when short name or full name are empty ************/
/*****************************************************************************/

void Ale_CreateAlertYouMustSpecifyShrtNameAndFullName (void)
  {
   extern const char *Txt_You_must_specify_the_short_name_and_the_full_name;

   Ale_CreateAlert (Ale_WARNING,NULL,
		    Txt_You_must_specify_the_short_name_and_the_full_name);
  }
