// swad_rubric.c: assessment rubrics

/*
    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 ***********************************/
/*****************************************************************************/

#include <stddef.h>		// For NULL
#include <stdlib.h>		// For free, malloc

#include "swad_action_list.h"
#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_constant.h"
#include "swad_database.h"
#include "swad_error.h"
#include "swad_figure.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_pagination.h"
#include "swad_parameter.h"
#include "swad_parameter_code.h"
#include "swad_resource.h"
#include "swad_rubric.h"
#include "swad_rubric_criteria.h"
#include "swad_rubric_database.h"

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

extern struct Globals Gbl;

/*****************************************************************************/
/***************************** Private constants *****************************/
/*****************************************************************************/

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

static void Rub_PutIconsListRubrics (void *Rubrics);
static void Rub_PutIconToCreateNewRubric (struct Rub_Rubrics *Rubrics);
static void Rub_PutParsToCreateNewRubric (void *Rubrics);

static void Rub_PutIconsViewingOneRubric (void *Rubrics);
static void Rub_PutIconsEditingOneRubric (void *Rubrics);
static void Rub_PutIconsToRemEditOneRubric (struct Rub_Rubrics *Rubrics);
static void Rub_WriteAuthor (const struct Rub_Rubric *Rubric);

static void Rub_RemoveRubricFromAllTables (long RubCod);

static void Rub_PutFormEditionRubric (struct Rub_Rubrics *Rubrics,
			              OldNew_OldNew_t OldNewRubric);
static void Rub_ReceiveRubricFieldsFromForm (struct Rub_Rubric *Rubric);
static Err_SuccessOrError_t Rub_CheckRubricFieldsReceivedFromForm (const struct Rub_Rubric *Rubric);

static void Rub_CreateRubric (struct Rub_Rubric *Rubric);
static void Rub_UpdateRubric (struct Rub_Rubric *Rubric);

static Err_SuccessOrError_t Rub_CheckIfRecursiveTree (long RubCod,struct Rub_Node **TOS);

/*****************************************************************************/
/*************************** Reset rubrics context ***************************/
/*****************************************************************************/

void Rub_ResetRubrics (struct Rub_Rubrics *Rubrics)
  {
   Rubrics->LstIsRead     = false;	// List not read from database...
   Rubrics->Num           = 0;		// Total number of rubrics
   Rubrics->Lst           = NULL;	// List of rubrics
   Rubrics->CurrentPage   = 0;
   // Rubrics->Rubric.RubCod = -1L;	// Current/selected rubric code
  }

/*****************************************************************************/
/************************ Rubric constructor/destructor **********************/
/*****************************************************************************/

void Rub_RubricConstructor (struct Rub_Rubric *Rubric)
  {
   /***** Initialize to empty rubric *****/
   Rubric->RubCod   = -1L;
   Rubric->HieCod   = -1L;	// Course code
   Rubric->UsrCod   = -1L;
   Rubric->Title[0] = '\0';

   /***** Allocate memory for rubric text *****/
   if ((Rubric->Txt = malloc (Cns_MAX_BYTES_TEXT + 1)) == NULL)
      Err_NotEnoughMemoryExit ();
   Rubric->Txt[0] = '\0';
  }

void Rub_RubricDestructor (struct Rub_Rubric *Rubric)
  {
   /***** Free memory used for rubric text *****/
   if (Rubric->Txt != NULL)
     {
      free (Rubric->Txt);
      Rubric->Txt = NULL;
     }
  }

/*****************************************************************************/
/***************************** Show all rubrics ******************************/
/*****************************************************************************/

void Rub_SeeAllRubrics (void)
  {
   struct Rub_Rubrics Rubrics;

   /***** Reset rubrics context *****/
   Rub_ResetRubrics (&Rubrics);

   /***** Get parameters *****/
   Rub_GetPars (&Rubrics,Rub_DONT_CHECK_RUB_COD);

   /***** Show all rubrics *****/
   Rub_ListAllRubrics (&Rubrics);
  }

/*****************************************************************************/
/******************************* List all rubrics ****************************/
/*****************************************************************************/

void Rub_ListAllRubrics (struct Rub_Rubrics *Rubrics)
  {
   extern const char *Hlp_ASSESSMENT_Rubrics;
   extern const char *Txt_Rubrics;
   extern const char *Txt_Rubric;
   extern const char *Txt_No_rubrics;
   struct Pag_Pagination Pagination;
   unsigned NumRubric;

   /***** Get list of rubrics *****/
   Rub_GetListRubrics (Rubrics);

   /***** Compute variables related to pagination *****/
   Pagination.NumItems = Rubrics->Num;
   Pagination.CurrentPage = Rubrics->CurrentPage;
   Pag_CalculatePagination (&Pagination);
   Rubrics->CurrentPage = Pagination.CurrentPage;

   /***** Begin box *****/
   Box_BoxBegin (Txt_Rubrics,Rub_PutIconsListRubrics,Rubrics,
                 Hlp_ASSESSMENT_Rubrics,Box_NOT_CLOSABLE);

      /***** Write links to pages *****/
      Pag_WriteLinksToPagesCentered (Pag_RUBRICS,&Pagination,
				     Rubrics,-1L);

      if (Rubrics->Num)
	{
	 /***** Begin table *****/
	 HTM_TABLE_BeginWideMarginPadding (5);

	    /***** Table head *****/
	    HTM_TR_Begin (NULL);

               HTM_TH_Span (NULL	,HTM_HEAD_CENTER,1,1,"CONTEXT_COL");	// Column for contextual icons
	       HTM_TH_Span (Txt_Rubric	,HTM_HEAD_LEFT	,1,2,NULL);

	       // HTM_TH (Txt_Criteria,HTM_HEAD_RIGHT);

	    HTM_TR_End ();

	    /***** Write all rubrics *****/
	    for (NumRubric  = Pagination.FirstItemVisible;
		 NumRubric <= Pagination.LastItemVisible;
		 NumRubric++)
	      {
	       /***** Get rubric data *****/
	       Rub_RubricConstructor (&Rubrics->Rubric);
	       Rubrics->Rubric.RubCod = Rubrics->Lst[NumRubric - 1];
	       Rub_GetRubricDataByCod (&Rubrics->Rubric);

	       /***** Show main data of this rubric *****/
	       Rub_ShowRubricMainData (Rubrics,Lay_SHOWING_SEVERAL);

	       /***** Free memory used for rubric *****/
	       Rub_RubricDestructor (&Rubrics->Rubric);
	      }

	 /***** End table *****/
	 HTM_TABLE_End ();
	}
      else	// No rubrics created
	 Ale_ShowAlert (Ale_INFO,Txt_No_rubrics);

      /***** Write again links to pages *****/
      Pag_WriteLinksToPagesCentered (Pag_RUBRICS,&Pagination,
				     Rubrics,-1L);

   /***** End box *****/
   Box_BoxEnd ();

   /***** Free list of rubrics *****/
   Rub_FreeListRubrics (Rubrics);
  }

/*****************************************************************************/
/************************ Check if I can edit rubrics ************************/
/*****************************************************************************/

Usr_Can_t Rub_CheckIfICanEditRubrics (void)
  {
   static Usr_Can_t ICanEditRubrics[Rol_NUM_ROLES] =
     {
      [Rol_TCH    ] = Usr_CAN,
      [Rol_SYS_ADM] = Usr_CAN,
     };

   return ICanEditRubrics[Gbl.Usrs.Me.Role.Logged];
  }

/*****************************************************************************/
/*********************** Check if edition is possible ************************/
/*****************************************************************************/

Usr_Can_t Rub_CheckIfEditable (void)
  {
   if (Rub_CheckIfICanEditRubrics () == Usr_CAN)
     {
      /***** Rubric is editable only if ... *****/
      // TODO: Change to control that a rubric is not edited in some circunstances?
      /*
      return Rubric->NumCriteria == 0 ||
	     Rubric->NumCriteria != 0;
      */
      return Usr_CAN;
     }

   return Usr_CAN_NOT;	// Questions are not editable
  }

/*****************************************************************************/
/****************** Put contextual icons in list of rubrics ******************/
/*****************************************************************************/

static void Rub_PutIconsListRubrics (void *Rubrics)
  {
   if (Rubrics)
     {
      if (Rub_CheckIfICanEditRubrics () == Usr_CAN)
	{
         /***** Put icon to create a new rubric *****/
	 Rub_PutIconToCreateNewRubric ((struct Rub_Rubrics *) Rubrics);

	 /***** Put icon to view resource clipboard *****/
	 Rsc_PutIconToViewClipboard ();
	}

      /***** Put icon to get resource link *****/
      Ico_PutContextualIconToGetLink (ActReqLnkRub,NULL,Rub_PutPars,Rubrics);

      /***** Put icon to show a figure *****/
      Fig_PutIconToShowFigure (Fig_RUBRICS);
     }
  }

/*****************************************************************************/
/********************** Put icon to create a new rubric **********************/
/*****************************************************************************/

static void Rub_PutIconToCreateNewRubric (struct Rub_Rubrics *Rubrics)
  {
   Ico_PutContextualIconToAdd (ActFrmNewRub,NULL,
                               Rub_PutParsToCreateNewRubric,Rubrics);
  }

/*****************************************************************************/
/******************* Put parameters to create a new rubric *******************/
/*****************************************************************************/

static void Rub_PutParsToCreateNewRubric (void *Rubrics)
  {
   if (Rubrics)
      Pag_PutParPagNum (Pag_RUBRICS,((struct Rub_Rubrics *) Rubrics)->CurrentPage);
  }

/*****************************************************************************/
/****************************** Show one rubric ******************************/
/*****************************************************************************/

void Rub_SeeOneRubric (void)
  {
   struct Rub_Rubrics Rubrics;

   /***** Reset rubrics context *****/
   Rub_ResetRubrics (&Rubrics);
   Rub_RubricConstructor (&Rubrics.Rubric);

   /***** Get parameters *****/
   Rub_GetPars (&Rubrics,Rub_CHECK_RUB_COD);

   /***** Get rubric data *****/
   Rub_GetRubricDataByCod (&Rubrics.Rubric);

   /***** Show rubric *****/
   Rub_ShowOnlyOneRubric (&Rubrics);

   /***** Free memory used for rubric *****/
   Rub_RubricDestructor (&Rubrics.Rubric);
  }

/*****************************************************************************/
/******************************* Show one rubric *****************************/
/*****************************************************************************/

void Rub_ShowOnlyOneRubric (struct Rub_Rubrics *Rubrics)
  {
   extern const char *Hlp_ASSESSMENT_Rubrics;
   extern const char *Txt_Rubric;
   struct Rub_Node *TOS = NULL;

   /***** Begin box *****/
   Box_BoxBegin (Rubrics->Rubric.Title[0] ? Rubrics->Rubric.Title :
					    Txt_Rubric,
                 Rub_PutIconsViewingOneRubric,Rubrics,
		 Hlp_ASSESSMENT_Rubrics,Box_NOT_CLOSABLE);

      /***** Show main data of this rubric *****/
      Rub_ShowRubricMainData (Rubrics,Lay_SHOWING_ONLY_ONE);

      /***** Check if rubric tree is correct *****/
      if (Rub_CheckIfRecursiveTree (Rubrics->Rubric.RubCod,&TOS) == Err_ERROR)
	 Err_RecursiveRubric ();

      /***** Write criteria of this rubric *****/
      RubCri_ListCriteriaForSeeing (Rubrics);

   /***** End box *****/
   Box_BoxEnd ();
  }

/*****************************************************************************/
/********* Show a pair of rows with the main data of a given rubric **********/
/*****************************************************************************/

void Rub_ShowRubricMainData (struct Rub_Rubrics *Rubrics,
                             Lay_ShowingOneOrSeveral_t ShowingOneOrSeveral)
  {
   extern const char *Txt_Number_of_criteria;

   /***** Begin box and table *****/
   if (ShowingOneOrSeveral == Lay_SHOWING_ONLY_ONE)
      HTM_TABLE_BeginWidePadding (2);

   /***** Begin first row of this rubric *****/
   HTM_TR_Begin (NULL);

      /***** Icons related to this rubric *****/
      if (ShowingOneOrSeveral == Lay_SHOWING_SEVERAL)
	{
	 HTM_TD_Begin ("rowspan=\"2\" class=\"CONTEXT_COL %s\"",
		       The_GetColorRows ());
	    Rub_PutIconsToRemEditOneRubric (Rubrics);
	 HTM_TD_End ();
	}

      /***** Rubric title and main data *****/
      switch (ShowingOneOrSeveral)
	{
	 case Lay_SHOWING_ONLY_ONE:
	    HTM_TD_Begin ("colspan=\"2\" class=\"LT\"");
	    break;
	 case Lay_SHOWING_SEVERAL:
	    HTM_TD_Begin ("colspan=\"2\" class=\"LT %s\"",The_GetColorRows ());
	    break;
	}

      /* Rubric title */
      Frm_BeginForm (ActSeeOneRub);
	 Rub_PutPars (Rubrics);
	 HTM_BUTTON_Submit_Begin (Act_GetActionText (ActSeeOneRub),NULL,
				  "class=\"LT BT_LINK ASG_TITLE_%s\"",
				  The_GetSuffix ());
	    HTM_Txt (Rubrics->Rubric.Title);
	 HTM_BUTTON_End ();
      Frm_EndForm ();

      /* Number of criteria */
      HTM_DIV_Begin ("class=\"ASG_GRP_%s\"",The_GetSuffix ());
	 HTM_Txt (Txt_Number_of_criteria); HTM_Colon (); HTM_NBSP ();
	 HTM_Unsigned (Rub_DB_GetNumCriteriaInRubric (Rubrics->Rubric.RubCod));
      HTM_DIV_End ();

      HTM_TD_End ();

   /***** End 1st row of this rubric *****/
   HTM_TR_End ();

   /***** Begin 2nd row of this rubric *****/
   HTM_TR_Begin (NULL);

      /***** Author of the rubric *****/
      switch (ShowingOneOrSeveral)
	{
	 case Lay_SHOWING_ONLY_ONE:
	    HTM_TD_Begin ("class=\"LT\"");
	    break;
	 case Lay_SHOWING_SEVERAL:
	    HTM_TD_Begin ("class=\"LT %s\"",The_GetColorRows ());
	    break;
	}
      Rub_WriteAuthor (&Rubrics->Rubric);
      HTM_TD_End ();

      /***** Text of the rubric *****/
      switch (ShowingOneOrSeveral)
	{
	 case Lay_SHOWING_ONLY_ONE:
	    HTM_TD_Begin ("class=\"LT\"");
	    break;
	 case Lay_SHOWING_SEVERAL:
	    HTM_TD_Begin ("class=\"LT %s\"",The_GetColorRows ());
	    break;
	}
      Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
			Rubrics->Rubric.Txt,Cns_MAX_BYTES_TEXT,
			Str_DONT_REMOVE_SPACES);
      ALn_InsertLinks (Rubrics->Rubric.Txt,Cns_MAX_BYTES_TEXT,60);	// Insert links
      HTM_DIV_Begin ("class=\"PAR DAT_%s\"",The_GetSuffix ());
	 HTM_Txt (Rubrics->Rubric.Txt);
      HTM_DIV_End ();
      HTM_TD_End ();

   /***** End 2nd row of this rubric *****/
   HTM_TR_End ();

   /***** End table *****/
   switch (ShowingOneOrSeveral)
     {
      case Lay_SHOWING_ONLY_ONE:
	 HTM_TABLE_End ();
	 break;
      case Lay_SHOWING_SEVERAL:
	 The_ChangeRowColor ();
	 break;
     }
  }

/*****************************************************************************/
/*************** Put icons when viewing or editing one rubric ****************/
/*****************************************************************************/

static void Rub_PutIconsViewingOneRubric (void *Rubrics)
  {
   if (Rubrics)
      Rub_PutIconsToRemEditOneRubric (Rubrics);
  }

static void Rub_PutIconsEditingOneRubric (void *Rubrics)
  {
   if (Rubrics)
      /***** Icon to view rubric *****/
      Ico_PutContextualIconToView (ActSeeOneRub,NULL,Rub_PutPars,Rubrics);
  }

static void Rub_PutIconsToRemEditOneRubric (struct Rub_Rubrics *Rubrics)
  {
   if (Rub_CheckIfICanEditRubrics () == Usr_CAN)
     {
      /***** Icon to remove rubric *****/
      Ico_PutContextualIconToRemove (ActReqRemRub,NULL,Rub_PutPars,Rubrics);

      /***** Icon to edit rubric *****/
      Ico_PutContextualIconToEdit (ActEdiOneRub,NULL,Rub_PutPars,Rubrics);

      /***** Icon to get resource link *****/
      Ico_PutContextualIconToGetLink (ActReqLnkRub,NULL,Rub_PutPars,Rubrics);
     }
  }

/*****************************************************************************/
/*********************** Write the author of a rubric ************************/
/*****************************************************************************/

static void Rub_WriteAuthor (const struct Rub_Rubric *Rubric)
  {
   Usr_WriteAuthor1Line (Rubric->UsrCod,HidVis_VISIBLE);
  }

/*****************************************************************************/
/*********************** Params used to edit a rubric ************************/
/*****************************************************************************/

void Rub_PutPars (void *Rubrics)
  {
   if (Rubrics)
     {
      ParCod_PutPar (ParCod_Rub,((struct Rub_Rubrics *) Rubrics)->Rubric.RubCod);
      Pag_PutParPagNum (Pag_RUBRICS,((struct Rub_Rubrics *) Rubrics)->CurrentPage);
     }
  }

/*****************************************************************************/
/****************** Get parameters used to edit a rubric *********************/
/*****************************************************************************/

void Rub_GetPars (struct Rub_Rubrics *Rubrics,Rub_CheckRubCod_t CheckRubCod)
  {
   long (*GetExaCod[Rub_NUM_CHECK_RUB_COD]) (ParCod_Param_t ParCode) =
     {
      [Rub_DONT_CHECK_RUB_COD] = ParCod_GetPar,
      [Rub_CHECK_RUB_COD     ] = ParCod_GetAndCheckPar,
     };

   /***** Get other parameters *****/
   Rubrics->CurrentPage = Pag_GetParPagNum (Pag_RUBRICS);

   /***** Get rubric code *****/
   Rubrics->Rubric.RubCod = GetExaCod[CheckRubCod] (ParCod_Rub);
  }

/*****************************************************************************/
/************************* Get list of all rubrics ***************************/
/*****************************************************************************/
// Txt is not retrieved from database

void Rub_GetListRubrics (struct Rub_Rubrics *Rubrics)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumRubric;

   /***** Free list of rubrics *****/
   if (Rubrics->LstIsRead)
      Rub_FreeListRubrics (Rubrics);

   /***** Get list of rubrics from database *****/
   if ((Rubrics->Num = Rub_DB_GetListRubrics (&mysql_res))) // Rubrics found...
     {
      /***** Create list of rubrics *****/
      if ((Rubrics->Lst = malloc ((size_t) Rubrics->Num *
                                  sizeof (*Rubrics->Lst))) == NULL)
         Err_NotEnoughMemoryExit ();

      /***** Get the rubrics *****/
      for (NumRubric = 0;
	   NumRubric < Rubrics->Num;
	   NumRubric++)
        {
	 /* Get row */
	 row = mysql_fetch_row (mysql_res);

	 /* Get code of the rubric (row[0]) */
	 Rubrics->Lst[NumRubric] = Str_ConvertStrCodToLongCod (row[0]);
        }
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   Rubrics->LstIsRead = true;
  }

/*****************************************************************************/
/*********************** Get rubric data using its code **********************/
/*****************************************************************************/

void Rub_GetRubricDataByCod (struct Rub_Rubric *Rubric)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;

   /***** Get data of rubric from database *****/
   switch (Rub_DB_GetRubricDataByCod (&mysql_res,Rubric->RubCod))
     {
      case Exi_EXISTS:
	 /* Get row */
	 row = mysql_fetch_row (mysql_res);

	 /* Get code of the rubric (row[0]), course (row[1] and author (row[2]) */
	 Rubric->RubCod = Str_ConvertStrCodToLongCod (row[0]);
	 Rubric->HieCod = Str_ConvertStrCodToLongCod (row[1]);
	 Rubric->UsrCod = Str_ConvertStrCodToLongCod (row[2]);

	 /* Get the title of the rubric (row[3]) */
	 Str_Copy (Rubric->Title,row[3],sizeof (Rubric->Title) - 1);

	 /* Get rubric text */
	 Rub_DB_GetRubricTxt (Rubric);
	 break;
      case Exi_DOES_NOT_EXIST:
      default:
	 /***** Initialize to empty rubric *****/
	 Rubric->RubCod   = -1L;
	 Rubric->HieCod   = -1L;
	 Rubric->UsrCod   = -1L;
	 Rubric->Title[0] = '\0';
	 Rubric->Txt[0]   = '\0';
	 break;
     }

   /* Free structure that stores the query result */
   DB_FreeMySQLResult (&mysql_res);
  }

/*****************************************************************************/
/*************************** Free list of rubrics ****************************/
/*****************************************************************************/

void Rub_FreeListRubrics (struct Rub_Rubrics *Rubrics)
  {
   if (Rubrics->LstIsRead && Rubrics->Lst)
     {
      /***** Free memory used by the list of rubrics *****/
      free (Rubrics->Lst);
      Rubrics->Lst       = NULL;
      Rubrics->Num       = 0;
      Rubrics->LstIsRead = false;
     }
  }

/*****************************************************************************/
/************** Ask for confirmation of removing of a rubric *****************/
/*****************************************************************************/

void Rub_AskRemRubric (void)
  {
   extern const char *Txt_Do_you_really_want_to_remove_the_rubric_X;
   struct Rub_Rubrics Rubrics;

   /***** Reset rubrics context *****/
   Rub_ResetRubrics (&Rubrics);
   Rub_RubricConstructor (&Rubrics.Rubric);

   /***** Get parameters *****/
   Rub_GetPars (&Rubrics,Rub_CHECK_RUB_COD);

   /***** Get data of the rubric from database *****/
   Rub_GetRubricDataByCod (&Rubrics.Rubric);
   if (Rub_CheckIfICanEditRubrics () == Usr_CAN_NOT)
      Err_NoPermissionExit ();

   /***** Show criterion and button to remove rubric *****/
   Ale_ShowAlertRemove (ActRemRub,NULL,
                        Rub_PutPars,&Rubrics,
			Txt_Do_you_really_want_to_remove_the_rubric_X,
                        Rubrics.Rubric.Title);

   /***** Show rubrics again *****/
   Rub_ListAllRubrics (&Rubrics);

   /***** Free memory used for rubric *****/
   Rub_RubricDestructor (&Rubrics.Rubric);
  }

/*****************************************************************************/
/****************************** Remove a rubric ******************************/
/*****************************************************************************/

void Rub_RemoveRubric (void)
  {
   extern const char *Txt_Rubric_X_removed;
   struct Rub_Rubrics Rubrics;

   /***** Reset rubrics context *****/
   Rub_ResetRubrics (&Rubrics);
   Rub_RubricConstructor (&Rubrics.Rubric);

   /***** Get parameters *****/
   Rub_GetPars (&Rubrics,Rub_CHECK_RUB_COD);

   /***** Get data of the rubric from database *****/
   Rub_GetRubricDataByCod (&Rubrics.Rubric);
   if (Rub_CheckIfICanEditRubrics () == Usr_CAN_NOT)
      Err_NoPermissionExit ();

   /***** Remove rubric from all tables *****/
   Rub_RemoveRubricFromAllTables (Rubrics.Rubric.RubCod);

   /***** Write message to show the change made *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_Rubric_X_removed,Rubrics.Rubric.Title);

   /***** Show rubrics again *****/
   Rub_ListAllRubrics (&Rubrics);

   /***** Free memory used for rubric *****/
   Rub_RubricDestructor (&Rubrics.Rubric);
  }

/*****************************************************************************/
/********************** Remove rubric from all tables ************************/
/*****************************************************************************/

static void Rub_RemoveRubricFromAllTables (long RubCod)
  {
   /***** Remove rubric criteria *****/
   Rub_DB_RemoveRubricCriteria (RubCod);

   /***** Remove rubric *****/
   Rub_DB_RemoveRubric (RubCod);
  }

/*****************************************************************************/
/********************** Remove all rubrics of a course ***********************/
/*****************************************************************************/

void Rub_RemoveCrsRubrics (long HieCod)
  {
   /***** Remove the criteria in rubrics *****/
   Rub_DB_RemoveCrsRubricCriteria (HieCod);

   /***** Remove the rubrics *****/
   Rub_DB_RemoveCrsRubrics (HieCod);
  }

/*****************************************************************************/
/*************** Request the creation or edition of a rubric *****************/
/*****************************************************************************/

void Rub_ReqCreatOrEditRubric (void)
  {
   struct Rub_Rubrics Rubrics;
   OldNew_OldNew_t OldNewRubric;

   /***** Check if I can edit rubrics *****/
   if (Rub_CheckIfICanEditRubrics () == Usr_CAN_NOT)
      Err_NoPermissionExit ();

   /***** Reset rubrics context *****/
   Rub_ResetRubrics (&Rubrics);
   Rub_RubricConstructor (&Rubrics.Rubric);
   RubCri_ResetCriterion (&Rubrics.Criterion);

   /***** Get parameters *****/
   Rub_GetPars (&Rubrics,Rub_DONT_CHECK_RUB_COD);
   Rubrics.Criterion.RubCod = Rubrics.Rubric.RubCod;
   OldNewRubric = Rubrics.Rubric.RubCod > 0 ? OldNew_OLD :
					      OldNew_NEW;

   /***** Get rubric data from database *****/
   if (OldNewRubric == OldNew_OLD)
      Rub_GetRubricDataByCod (&Rubrics.Rubric);

   /***** Put form to create/edit a rubric and show criteria *****/
   Rub_PutFormsOneRubric (&Rubrics,OldNewRubric);

   /***** Free memory used for rubric *****/
   Rub_RubricDestructor (&Rubrics.Rubric);
  }

/*****************************************************************************/
/******************** Put forms to create/edit a rubric **********************/
/*****************************************************************************/

void Rub_PutFormsOneRubric (struct Rub_Rubrics *Rubrics,
			    OldNew_OldNew_t OldNewRubric)
  {
   extern const char *Hlp_ASSESSMENT_Rubrics_new_rubric;
   extern const char *Hlp_ASSESSMENT_Rubrics_edit_rubric;
   extern const char *Txt_Rubric;
   static void (*FunctionToDrawContextualIcons[OldNew_NUM_OLD_NEW]) (void *Args) =
     {
      [OldNew_OLD] = Rub_PutIconsEditingOneRubric,
      [OldNew_NEW] = NULL,
     };
   static const char **HelpLink[OldNew_NUM_OLD_NEW] =
     {
      [OldNew_OLD] = &Hlp_ASSESSMENT_Rubrics_edit_rubric,
      [OldNew_NEW] = &Hlp_ASSESSMENT_Rubrics_new_rubric,
     };
   struct Rub_Node *TOS = NULL;

   /***** Begin box *****/
   Box_BoxBegin (Rubrics->Rubric.Title[0] ? Rubrics->Rubric.Title :
					    Txt_Rubric,
		 FunctionToDrawContextualIcons[OldNewRubric],Rubrics,
		 *HelpLink[OldNewRubric],Box_NOT_CLOSABLE);

      /***** Put form to create/edit a rubric *****/
      Rub_PutFormEditionRubric (Rubrics,OldNewRubric);

      /***** Check if rubric tree is correct *****/
      if (Rub_CheckIfRecursiveTree (Rubrics->Rubric.RubCod,&TOS) == Err_ERROR)
	 Err_RecursiveRubric ();

      /***** Show list of criteria inside box *****/
      if (OldNewRubric == OldNew_OLD)
	 RubCri_ListCriteriaForEdition (Rubrics);

   /***** End box ****/
   Box_BoxEnd ();

   /***** Show rubrics again outside box *****/
   if (OldNewRubric == OldNew_NEW)
     {
      HTM_BR ();
      Rub_ListAllRubrics (Rubrics);
     }
  }

/*****************************************************************************/
/******************** Put a form to create/edit a rubric *********************/
/*****************************************************************************/

static void Rub_PutFormEditionRubric (struct Rub_Rubrics *Rubrics,
			              OldNew_OldNew_t OldNewRubric)
  {
   extern const char *Txt_Title;
   extern const char *Txt_Description;
   static struct
     {
      Act_Action_t Action;
      Btn_Button_t Button;
     } Forms[OldNew_NUM_OLD_NEW] =
     {
      [OldNew_OLD] = {ActChgRub,Btn_SAVE_CHANGES},
      [OldNew_NEW] = {ActNewRub,Btn_CREATE      }
     };

   /***** Begin form *****/
   Frm_BeginForm (Forms[OldNewRubric].Action);
      Rub_PutPars (Rubrics);

      /***** Begin table *****/
      HTM_TABLE_BeginCenterPadding (2);

	 /***** Rubric title *****/
	 HTM_TR_Begin (NULL);

	    /* Label */
	    Frm_LabelColumn ("Frm_C1 RT","Title",Txt_Title);

	    /* Data */
	    HTM_TD_Begin ("class=\"Frm_C2 LT\"");
	       HTM_INPUT_TEXT ("Title",Rub_MAX_CHARS_TITLE,Rubrics->Rubric.Title,
			       HTM_REQUIRED,
			       "id=\"Title\" class=\"Frm_C2_INPUT INPUT_%s\"",
			       The_GetSuffix ());
	    HTM_TD_End ();

	 HTM_TR_End ();

	 /***** Rubric text *****/
	 HTM_TR_Begin (NULL);

	    /* Label */
	    Frm_LabelColumn ("Frm_C1 RT","Txt",Txt_Description);

	    /* Data */
	    HTM_TD_Begin ("class=\"Frm_C2 LT\"");
	       HTM_TEXTAREA_Begin (HTM_NO_ATTR,
				   "id=\"Txt\" name=\"Txt\" rows=\"5\""
				   " class=\"Frm_C2_INPUT INPUT_%s\"",
				   The_GetSuffix ());
		  HTM_Txt (Rubrics->Rubric.Txt);
	       HTM_TEXTAREA_End ();
	    HTM_TD_End ();

	 HTM_TR_End ();

      /***** End table ****/
      HTM_TABLE_End ();

      /***** Send button *****/
      Btn_PutButton (Forms[OldNewRubric].Button,NULL);

   /***** End form *****/
   Frm_EndForm ();
  }

/*****************************************************************************/
/********************* Receive form to create a new rubric *******************/
/*****************************************************************************/

void Rub_ReceiveRubric (void)
  {
   struct Rub_Rubrics Rubrics;
   OldNew_OldNew_t OldNewRubric;

   /***** Check if I can edit rubrics *****/
   if (Rub_CheckIfICanEditRubrics () == Usr_CAN_NOT)
      Err_NoPermissionExit ();

   /***** Reset rubrics context *****/
   Rub_ResetRubrics (&Rubrics);
   Rub_RubricConstructor (&Rubrics.Rubric);
   RubCri_ResetCriterion (&Rubrics.Criterion);

   /***** Get parameters *****/
   Rub_GetPars (&Rubrics,Rub_DONT_CHECK_RUB_COD);
   Rubrics.Criterion.RubCod = Rubrics.Rubric.RubCod;
   OldNewRubric = Rubrics.Rubric.RubCod > 0 ? OldNew_OLD :
					      OldNew_NEW;

   /***** Get all current rubric data from database *****/
   // Some data, not received from form,
   // are necessary to show rubric and criteria again
   if (OldNewRubric == OldNew_OLD)
      Rub_GetRubricDataByCod (&Rubrics.Rubric);

   /***** Overwrite some rubric data with the data received from form *****/
   Rub_ReceiveRubricFieldsFromForm (&Rubrics.Rubric);
   if (Rub_CheckRubricFieldsReceivedFromForm (&Rubrics.Rubric) == Err_SUCCESS)
      /***** Create a new rubric or update an existing one *****/
      switch (OldNewRubric)
	{
	 case OldNew_OLD:
	    Rub_UpdateRubric (&Rubrics.Rubric);	// Update rubric data in database
	    break;
	 case OldNew_NEW:
	 default:
	    Rub_CreateRubric (&Rubrics.Rubric);	// Add new rubric to database
	    OldNewRubric = OldNew_OLD;
	    break;
	}

   /***** Show pending alerts */
   Ale_ShowAlerts (NULL);

   /***** Show current rubric and its criteria *****/
   Rub_PutFormsOneRubric (&Rubrics,OldNewRubric);

   /***** Free memory used for rubric *****/
   Rub_RubricDestructor (&Rubrics.Rubric);
  }

static void Rub_ReceiveRubricFieldsFromForm (struct Rub_Rubric *Rubric)
  {
   /***** Get rubric title *****/
   Par_GetParText ("Title",Rubric->Title,Rub_MAX_BYTES_TITLE);

   /***** Get rubric text *****/
   Par_GetParHTML ("Txt",Rubric->Txt,Cns_MAX_BYTES_TEXT);	// Store in HTML format (not rigorous)
  }

static Err_SuccessOrError_t Rub_CheckRubricFieldsReceivedFromForm (const struct Rub_Rubric *Rubric)
  {
   extern const char *Txt_Already_existed_a_game_with_the_title_X;
   Err_SuccessOrError_t SuccessOrError = Err_SUCCESS;

   /***** Check if title is correct *****/
   if (Rubric->Title[0])	// If there's a rubric title
     {
      /* If title of rubric was in database... */
      if (Rub_DB_CheckIfSimilarRubricExists (Rubric) == Exi_EXISTS)
	{
	 SuccessOrError = Err_ERROR;
	 Ale_CreateAlert (Ale_WARNING,NULL,
			  Txt_Already_existed_a_game_with_the_title_X,
			  Rubric->Title);
	}
     }
   else				// If there is not a rubric title
     {
      SuccessOrError = Err_ERROR;
      Ale_CreateAlertYouMustSpecifyTheTitle ();
     }

   return SuccessOrError;
  }

/*****************************************************************************/
/**************************** Create a new rubric ****************************/
/*****************************************************************************/

static void Rub_CreateRubric (struct Rub_Rubric *Rubric)
  {
   extern const char *Txt_Created_new_rubric_X;

   /***** Create a new rubric *****/
   Rubric->RubCod = Rub_DB_CreateRubric (Rubric);

   /***** Write success message *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_Created_new_rubric_X,Rubric->Title);
  }

/*****************************************************************************/
/************************* Update an existing rubric *************************/
/*****************************************************************************/

static void Rub_UpdateRubric (struct Rub_Rubric *Rubric)
  {
   extern const char *Txt_The_rubric_has_been_modified;

   /***** Update the data of the rubric *****/
   Rub_DB_UpdateRubric (Rubric);

   /***** Write success message *****/
   Ale_ShowAlert (Ale_SUCCESS,Txt_The_rubric_has_been_modified);
  }

/*****************************************************************************/
/********** Recursive function to compute the score of a criterion ***********/
/*****************************************************************************/
// Return Err_ERROR if rubric tree is recursive
/*                 Tree                                 Stack
                  _______                               ______
                 | Rub 1 |      TOS (Top Of Stack)____\|___5__|
                 |_______|                            /|_Prev_|
                    /|\                              _____/
                   / | \                            /   ______
                  /  |  \                           \_\|___4__|
                 /   |   \                            /|_Prev_|
                / ___|___ \                          _____/
               / | Rub 2 | \                        /   ______
     Handwritten |_______| Handwritten              \_\|___2__|
                    /|\                               /|_Prev_|
                   / | \                             _____/
                  /  |  \                           /   ______
                 /   |   \                          \_\|___1__|
                /    |    \                           /|_NULL_|
        _______/     |     \_______
       | Rub 3 |     |     | Rub 4 |
       |_______|Handwritten|_______|
            /                 /|\
           /                 / | \
          /                 /  |  \
         /                 /   |   \
        /                 / ___|___ \
       /                 / | Rub 5 | \
Handwritten    Handwritten |_______| Handwritten
                              /|\
                             / | \
                            /  |  \
                           /   |   \
                          /    |    \
                         /     |     \
              Handwritten Handwritten Handwritten
*/
static Err_SuccessOrError_t Rub_CheckIfRecursiveTree (long RubCod,struct Rub_Node **TOS)
  {
   Err_SuccessOrError_t RecursiveTree;
   MYSQL_RES *mysql_res;
   unsigned NumCriteria;
   unsigned NumCriterion;
   struct RubCri_Criterion Criterion;

   /***** Check that rubric is not yet in the stack *****/
   RecursiveTree = Rub_FindRubCodInStack (*TOS,RubCod) == Exi_EXISTS ? Err_ERROR :
								       Err_SUCCESS;

   if (RecursiveTree == Err_SUCCESS)
     {
      /***** Push rubric code in stack *****/
      Rub_PushRubCod (TOS,RubCod);

      /***** For each criteria in this rubric... *****/
      NumCriteria = Rub_DB_GetCriteria (&mysql_res,RubCod);
      for (NumCriterion = 0;
	   NumCriterion < NumCriteria;
	   NumCriterion++)
	{
	 /* Get criterion data */
	 RubCri_GetCriterionDataFromRow (mysql_res,&Criterion);

	 if (Criterion.Link.Type == Rsc_RUBRIC)
	    if (Rub_CheckIfRecursiveTree (Criterion.Link.Cod,TOS) == Err_ERROR)
	      {
	       RecursiveTree = Err_ERROR;
	       break;
	      }
	}

      /***** Free structure that stores the query result *****/
      DB_FreeMySQLResult (&mysql_res);

      /***** Pop rubric code from stack *****/
      Rub_PopRubCod (TOS);
     }

   return RecursiveTree;
  }


/*****************************************************************************/
/********************** Push/pop rubric code in stack ************************/
/*****************************************************************************/

void Rub_PushRubCod (struct Rub_Node **TOS,long RubCod)
  {
   struct Rub_Node *Node;

   /***** Save current top of stack *****/
   Node = *TOS;

   /***** Create top of stack node *****/
   if ((*TOS = malloc (sizeof (struct Rub_Node))) == NULL)
      Err_NotEnoughMemoryExit ();
   (*TOS)->RubCod = RubCod;
   (*TOS)->Prev = Node;		// Link to previous top of stack
  }

void Rub_PopRubCod (struct Rub_Node **TOS)
  {
   struct Rub_Node *Node;

   if (*TOS)
     {
      /***** Save current top of stack *****/
      Node = (*TOS)->Prev;

      /***** Free current top of stack node *****/
      free (*TOS);

      /***** Assign new top of stack *****/
      *TOS = Node;
     }
  }

/*****************************************************************************/
/************************ Find rubric code in stack **************************/
/*****************************************************************************/
// Return Exi_EXISTS if found

Exi_Exist_t Rub_FindRubCodInStack (const struct Rub_Node *TOS,long RubCod)
  {
   while (TOS)
     {
      if (TOS->RubCod == RubCod)
	 return Exi_EXISTS;

      TOS = TOS->Prev;
     }

   return Exi_DOES_NOT_EXIST;
  }

/*****************************************************************************/
/************************* Show one rubric to fill it ************************/
/*****************************************************************************/

void Rub_ShowRubricsToFill (Rsc_Type_t RscType,long RscCod,Usr_Can_t ICanFill,
			    Act_Action_t NextAction,
                            void (*FuncPars) (void *Args),void *Args,
			    unsigned NumRubrics,MYSQL_RES *mysql_res)
  {
   unsigned NumRub;
   struct Rub_Rubric Rubric;

   /***** Show each rubric *****/
   for (NumRub = 0;
	NumRub < NumRubrics;
	NumRub++)
     {
      /***** Get rubric data *****/
      Rub_RubricConstructor (&Rubric);
      Rubric.RubCod = DB_GetNextCode (mysql_res);
      Rub_GetRubricDataByCod (&Rubric);

      /***** Show rubric ready to fill them *****/
      /* First row for rubric title */
      HTM_TR_Begin (NULL);
	 HTM_TD_Begin ("colspan=\"8\" class=\"LT ASG_TITLE_%s\"",The_GetSuffix ());
	    HTM_Txt (Rubric.Title);
	 HTM_TD_End ();
      HTM_TR_End ();

      /* 2nd row for rubric text */
      HTM_TR_Begin (NULL);
	 HTM_TD_Begin ("colspan=\"8\" class=\"LT PAR DAT_%s\"",The_GetSuffix ());
	    Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
			      Rubric.Txt,Cns_MAX_BYTES_TEXT,
			      Str_DONT_REMOVE_SPACES);
	    ALn_InsertLinks (Rubric.Txt,Cns_MAX_BYTES_TEXT,60);	// Insert links
	    HTM_Txt (Rubric.Txt);
	 HTM_TD_End ();
      HTM_TR_End ();

      /* Write criteria of this rubric */
      RubCri_ListCriteriaToFill (RscType,RscCod,ICanFill,
	                         NextAction,FuncPars,Args,
	                         Rubric.RubCod);

      /***** Free memory used for rubric *****/
      Rub_RubricDestructor (&Rubric);
     }
  }

/*****************************************************************************/
/************************** Show stats about rubrics *************************/
/*****************************************************************************/

void Rub_GetAndShowRubricsStats (Hie_Level_t HieLvl)
  {
   extern const char *Hlp_ANALYTICS_Figures_rubrics;
   extern const char *Txt_FIGURE_TYPES[Fig_NUM_FIGURES];
   extern const char *Txt_Number_of_BR_rubrics;
   extern const char *Txt_Number_of_BR_courses_with_BR_rubrics;
   extern const char *Txt_Average_number_BR_of_rubrics_BR_per_course;
   extern const char *Txt_Average_number_BR_of_criteria_BR_per_rubric;
   unsigned NumRubrics;
   unsigned NumCoursesWithRubrics;
   double NumCriteriaPerRubric;

   /***** Get the number of rubrics from this location *****/
   if ((NumRubrics = Rub_DB_GetNumRubrics (HieLvl)))
     {
      NumCoursesWithRubrics = Rub_DB_GetNumCoursesWithRubrics (HieLvl);
      NumCriteriaPerRubric = Rub_DB_GetNumCriteriaPerRubric (HieLvl);
     }
   else
     {
      NumCoursesWithRubrics = 0;
      NumCriteriaPerRubric = 0.0;
     }

   /***** Begin box and table *****/
   Box_BoxTableBegin (Txt_FIGURE_TYPES[Fig_RUBRICS],NULL,NULL,
                      Hlp_ANALYTICS_Figures_rubrics,Box_NOT_CLOSABLE,2);

      /***** Write table heading *****/
      HTM_TR_Begin (NULL);
	 HTM_TH (Txt_Number_of_BR_rubrics                       ,HTM_HEAD_RIGHT);
	 HTM_TH (Txt_Number_of_BR_courses_with_BR_rubrics       ,HTM_HEAD_RIGHT);
	 HTM_TH (Txt_Average_number_BR_of_rubrics_BR_per_course ,HTM_HEAD_RIGHT);
	 HTM_TH (Txt_Average_number_BR_of_criteria_BR_per_rubric,HTM_HEAD_RIGHT);
      HTM_TR_End ();

      /***** Write number of rubrics *****/
      HTM_TR_Begin (NULL);
	 HTM_TD_Unsigned (NumRubrics);
	 HTM_TD_Unsigned (NumCoursesWithRubrics);
	 HTM_TD_Ratio (NumRubrics,NumCoursesWithRubrics);
	 HTM_TD_Double2Decimals (NumCriteriaPerRubric);
      HTM_TR_End ();

   /***** End table and box *****/
   Box_BoxTableEnd ();
  }
