// swad_course_database.c: edition of courses operations with database

/*
    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 asprintf
#include <stdio.h>		// For asprintf
#include <stdlib.h>		// For free

#include "swad_course.h"
#include "swad_course_config.h"
#include "swad_database.h"
#include "swad_error.h"
#include "swad_global.h"
#include "swad_hierarchy.h"
#include "swad_hierarchy_database.h"

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

extern struct Globals Gbl;

/*****************************************************************************/
/************* Add a new requested course to pending requests ****************/
/*****************************************************************************/

void Crs_DB_CreateCourse (struct Hie_Node *Crs,Hie_Status_t Status)
  {
   /***** Insert new course into pending requests *****/
   Crs->HieCod =
   DB_QueryINSERTandReturnCode ("can not create a new course",
				"INSERT INTO crs_courses"
				" (DegCod,Year,InsCrsCod,Status,RequesterUsrCod,"
				  "ShortName,FullName)"
				" VALUES"
				" (%ld,%u,'%s',%u,%ld,"
				  "'%s','%s')",
				Crs->PrtCod,
				Crs->Specific.Year,
				Crs->InstitutionalCod,
				(unsigned) Status,
				Gbl.Usrs.Me.UsrDat.UsrCod,
				Crs->ShrtName,
				Crs->FullName);
  }

/*****************************************************************************/
/******************* Get courses of a degree from database *******************/
/*****************************************************************************/

unsigned Crs_DB_GetAllCrss (MYSQL_RES **mysql_res)
  {
   return (unsigned)
   DB_QuerySELECT (mysql_res,"can not get courses",
		   "SELECT CrsCod"
		    " FROM crs_courses"
		" ORDER BY CrsCod");
  }

/*****************************************************************************/
/******************* Get courses of a degree from database *******************/
/*****************************************************************************/

unsigned Crs_DB_GetCrssInDeg (MYSQL_RES **mysql_res,long HieCod)
  {
   return (unsigned)
   DB_QuerySELECT (mysql_res,"can not get courses of a degree",
		   "SELECT CrsCod"
		    " FROM crs_courses"
		   " WHERE DegCod=%ld",
		   HieCod);
  }

/*****************************************************************************/
/******************** Get courses in current degree *********************/
/*****************************************************************************/

unsigned Crs_DB_GetCrssInCurrentDegBasic (MYSQL_RES **mysql_res)
  {
   return (unsigned)
   DB_QuerySELECT (mysql_res,"can not get courses of a degree",
		   "SELECT CrsCod,"	// row[0]
			  "ShortName"	// row[1]
		    " FROM crs_courses"
		   " WHERE DegCod=%ld"
		" ORDER BY ShortName",
		   Gbl.Hierarchy.Node[Hie_DEG].HieCod);
  }

/*****************************************************************************/
/******************** Get courses in current degree *********************/
/*****************************************************************************/

unsigned Crs_DB_GetCrssInCurrentDegFull (MYSQL_RES **mysql_res)
  {
   return (unsigned)
   DB_QuerySELECT (mysql_res,"can not get courses of a degree",
		   "SELECT CrsCod,"		// row[0]
			  "DegCod,"		// row[1]
			  "Year,"		// row[2]
			  "InsCrsCod,"		// row[3]
			  "Status,"		// row[4]
			  "RequesterUsrCod,"	// row[5]
			  "ShortName,"		// row[6]
			  "FullName"		// row[7]
		    " FROM crs_courses"
		   " WHERE DegCod=%ld"
		     " AND (Status & %u)=0"
		" ORDER BY Year,"
			  "ShortName",
		   Gbl.Hierarchy.Node[Hie_DEG].HieCod,
		   (unsigned) Hie_STATUS_BIT_REMOVED);	// All courses except those removed
  }

/*****************************************************************************/
/********************* Get data of a course from its code ********************/
/*****************************************************************************/

Exi_Exist_t Crs_DB_GetCourseDataByCod (MYSQL_RES **mysql_res,long HieCod)
  {
   return
   DB_QuerySELECTunique (mysql_res,"can not get data of a course",
			 "SELECT CrsCod,"		// row[0]
				"DegCod,"		// row[1]
				"Year,"			// row[2]
				"InsCrsCod,"		// row[3]
				"Status,"		// row[4]
				"RequesterUsrCod,"	// row[5]
				"ShortName,"		// row[6]
				"FullName"		// row[7]
			  " FROM crs_courses"
			 " WHERE CrsCod=%ld",
			 HieCod);
  }

/*****************************************************************************/
/************* Get the degree code of a course from its code *****************/
/*****************************************************************************/

long Crs_DB_GetDegCodOfCourseByCod (long HieCod)
  {
   /***** Trivial check: course code should be > 0 *****/
   if (HieCod <= 0)
      return -1L;

   /***** Get the degree code of a course from database *****/
   return DB_QuerySELECTCode ("can not get the degree of a course",
			      "SELECT DegCod"
			       " FROM crs_courses"
			      " WHERE CrsCod=%ld",
			      HieCod);
  }

/*****************************************************************************/
/******* Get the short names of degree and course from a course code *********/
/*****************************************************************************/

void Crs_DB_GetShortNamesByCod (long HieCod,
                                char CrsShortName[Nam_MAX_BYTES_SHRT_NAME + 1],
                                char DegShortName[Nam_MAX_BYTES_SHRT_NAME + 1])
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;

   DegShortName[0] = CrsShortName[0] = '\0';

   /***** Trivial check: course code should be > 0 *****/
   if (HieCod <= 0)
      return;

   /***** Get the short name of a degree from database *****/
   if (DB_QuerySELECTunique (&mysql_res,"can not get the short name of a course",
			     "SELECT crs_courses.ShortName,"	// row[0]
				    "deg_degrees.ShortName"	// row[1]
			      " FROM crs_courses,"
				    "deg_degrees"
			     " WHERE crs_courses.CrsCod=%ld"
			       " AND crs_courses.DegCod=deg_degrees.DegCod",
			     HieCod) == Exi_EXISTS)
     {
      /***** Get the course short name and degree short name *****/
      row = mysql_fetch_row (mysql_res);
      Str_Copy (CrsShortName,row[0],Nam_MAX_BYTES_SHRT_NAME);
      Str_Copy (DegShortName,row[1],Nam_MAX_BYTES_SHRT_NAME);
     }

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

/*****************************************************************************/
/************** Check if course code exists in existing courses **************/
/*****************************************************************************/

Exi_Exist_t Crs_DB_CheckIfCrsCodExists (long HieCod)
  {
   return
   DB_QueryEXISTS ("can not check if a course already existed",
		   "SELECT EXISTS"
		   "(SELECT *"
		     " FROM crs_courses"
		    " WHERE CrsCod=%ld)",
		   HieCod);
  }

/*****************************************************************************/
/********** Check if the name of course exists in existing courses ***********/
/*****************************************************************************/

Exi_Exist_t Crs_DB_CheckIfCrsNameExistsInYearOfDeg (const char *FldName,const char *Name,
						    long Cod,long PrtCod,unsigned Year)
  {
   return
   DB_QueryEXISTS ("can not check if the name of a course already existed",
		   "SELECT EXISTS"
		   "(SELECT *"
		     " FROM crs_courses"
		    " WHERE DegCod=%ld"
		      " AND Year=%u"
		      " AND %s='%s'"
		      " AND CrsCod<>%ld)",
		   PrtCod,
		   Year,
		   FldName,Name,
		   Cod);
  }

/*****************************************************************************/
/************************** Write courses of a user **************************/
/*****************************************************************************/

unsigned Crs_DB_GetCrssOfAUsr (MYSQL_RES **mysql_res,long UsrCod,Rol_Role_t Role)
  {
   char *SubQuery;
   unsigned NumCrss;

   /***** Get courses of a user from database *****/
   if (Role == Rol_UNK)	// Role == Rol_UNK ==> any role
     {
      if (asprintf (&SubQuery,"%s","") < 0)
	 Err_NotEnoughMemoryExit ();
     }
   else
     {
      if (asprintf (&SubQuery," AND crs_users.Role=%u",(unsigned) Role) < 0)
	 Err_NotEnoughMemoryExit ();
     }
   NumCrss = (unsigned)
   DB_QuerySELECT (mysql_res,"can not get courses of a user",
		   "SELECT deg_degrees.DegCod,"		// row[0]
			  "crs_courses.CrsCod,"		// row[1]
			  "deg_degrees.ShortName,"	// row[2]
			  "deg_degrees.FullName,"	// row[3]
			  "crs_courses.Year,"		// row[4]
			  "crs_courses.FullName,"	// row[5]
			  "ctr_centers.ShortName,"	// row[6]
			  "crs_users.Accepted"		// row[7]
		    " FROM crs_users,"
		          "crs_courses,"
		          "deg_degrees,"
		          "ctr_centers"
		   " WHERE crs_users.UsrCod=%ld%s"
		     " AND crs_users.CrsCod=crs_courses.CrsCod"
		     " AND crs_courses.DegCod=deg_degrees.DegCod"
		     " AND deg_degrees.CtrCod=ctr_centers.CtrCod"
		" ORDER BY deg_degrees.FullName,"
		          "crs_courses.Year,"
		          "crs_courses.FullName",
		   UsrCod,SubQuery);

   /***** Free allocated memory for subquery *****/
   free (SubQuery);

   return NumCrss;
  }

/*****************************************************************************/
/************************** Get old courses from database ********************/
/*****************************************************************************/

unsigned Crs_DB_GetOldCrss (MYSQL_RES **mysql_res,unsigned long SecondsWithoutAccess)
  {
   return (unsigned)
   DB_QuerySELECT (mysql_res,"can not get old courses",
		   "SELECT CrsCod"
		    " FROM crs_last"
		   " WHERE LastTime<FROM_UNIXTIME(UNIX_TIMESTAMP()-%lu)"
		     " AND CrsCod NOT IN"
		         " (SELECT DISTINCT "
		                  "CrsCod"
			    " FROM crs_users)",
		   SecondsWithoutAccess);
  }

/*****************************************************************************/
/************************* Search courses in database ************************/
/*****************************************************************************/
// Returns number of courses found

unsigned Crs_DB_SearchCrss (MYSQL_RES **mysql_res,
                            const char SearchQuery[Sch_MAX_BYTES_SEARCH_QUERY + 1],
                            const char *RangeQuery)
  {
   return (unsigned)
   DB_QuerySELECT (mysql_res,"can not get courses",
		   "SELECT deg_degrees.DegCod,"		// row[0]
			  "crs_courses.CrsCod,"		// row[1]
			  "deg_degrees.ShortName,"	// row[2]
			  "deg_degrees.FullName,"	// row[3]
			  "crs_courses.Year,"		// row[4]
			  "crs_courses.FullName,"	// row[5]
			  "ctr_centers.ShortName"	// row[6]
		    " FROM crs_courses,"
			  "deg_degrees,"
			  "ctr_centers,"
			  "ins_instits,"
			  "cty_countrs"
		   " WHERE %s"
		     " AND crs_courses.DegCod=deg_degrees.DegCod"
		     " AND deg_degrees.CtrCod=ctr_centers.CtrCod"
		     " AND ctr_centers.InsCod=ins_instits.InsCod"
		     " AND ins_instits.CtyCod=cty_countrs.CtyCod"
		     "%s"
		" ORDER BY crs_courses.FullName,"
			  "ins_instits.FullName,"
			  "deg_degrees.FullName,"
			  "crs_courses.Year",
		   SearchQuery,
		   RangeQuery);
  }

/*****************************************************************************/
/********************** Get number of courses in system **********************/
/*****************************************************************************/

unsigned Crs_DB_GetNumCrssInSys (__attribute__((unused)) long HieCod)
  {
   return (unsigned) DB_GetNumRowsTable ("crs_courses");
  }

/*****************************************************************************/
/******************** Get number of courses in a country *********************/
/*****************************************************************************/

unsigned Crs_DB_GetNumCrssInCty (long HieCod)
  {
   return (unsigned)
   DB_QueryCOUNT ("can not get the number of courses in a country",
		  "SELECT COUNT(*)"
		   " FROM ins_instits,"
		         "ctr_centers,"
		         "deg_degrees,"
		         "crs_courses"
		  " WHERE ins_instits.CtyCod=%ld"
		    " AND ins_instits.InsCod=ctr_centers.InsCod"
		    " AND ctr_centers.CtrCod=deg_degrees.CtrCod"
		    " AND deg_degrees.DegCod=crs_courses.DegCod",
		  HieCod);
  }

/*****************************************************************************/
/****************** Get number of courses in an institution ******************/
/*****************************************************************************/

unsigned Crs_DB_GetNumCrssInIns (long HieCod)
  {
   return (unsigned)
   DB_QueryCOUNT ("can not get the number of courses in an institution",
		  "SELECT COUNT(*)"
		   " FROM ctr_centers,"
		         "deg_degrees,"
		         "crs_courses"
		  " WHERE ctr_centers.InsCod=%ld"
		    " AND ctr_centers.CtrCod=deg_degrees.CtrCod"
		    " AND deg_degrees.DegCod=crs_courses.DegCod",
		  HieCod);
  }

/*****************************************************************************/
/********************* Get number of courses in a center *********************/
/*****************************************************************************/

unsigned Crs_DB_GetNumCrssInCtr (long HieCod)
  {
   return (unsigned)
   DB_QueryCOUNT ("can not get the number of courses in a center",
		  "SELECT COUNT(*)"
		   " FROM deg_degrees,"
		         "crs_courses"
		  " WHERE deg_degrees.CtrCod=%ld"
		    " AND deg_degrees.DegCod=crs_courses.DegCod",
		  HieCod);
  }

/*****************************************************************************/
/********************* Get number of courses in a degree *********************/
/*****************************************************************************/

unsigned Crs_DB_GetNumCrssInDeg (long HieCod)
  {
   return (unsigned)
   DB_QueryCOUNT ("can not get the number of courses in a degree",
		  "SELECT COUNT(*)"
		   " FROM crs_courses"
		  " WHERE DegCod=%ld",
		  HieCod);
  }

/*****************************************************************************/
/******************** Get number of courses with users ***********************/
/*****************************************************************************/

unsigned Crs_DB_GetNumCrssWithUsrs (Hie_Level_t HieLvl,long HieCod,Rol_Role_t Role)
  {
   char SubQuery[128];

   Hie_DB_BuildSubquery (SubQuery,HieLvl,HieCod);

   return (unsigned)
   DB_QueryCOUNT ("can not get number of courses with users",
		  "SELECT COUNT(DISTINCT crs_courses.CrsCod)"
		   " FROM ins_instits,"
			 "ctr_centers,"
			 "deg_degrees,"
			 "crs_courses,"
			 "crs_users"
		  " WHERE %s"
			 "ins_instits.InsCod=ctr_centers.InsCod"
		    " AND ctr_centers.CtrCod=deg_degrees.CtrCod"
		    " AND deg_degrees.DegCod=crs_courses.DegCod"
		    " AND crs_courses.CrsCod=crs_users.CrsCod"
		    " AND crs_users.Role=%u",
		  SubQuery,
		  (unsigned) Role);
  }

/*****************************************************************************/
/**************** Get all courses of a user from database ********************/
/*****************************************************************************/
// Returns the number of rows of the result

unsigned Crs_DB_GetCrssFromUsr (MYSQL_RES **mysql_res,long UsrCod,long PrtCod)
  {
   if (PrtCod > 0)	// Courses in a degree
      return (unsigned)
      DB_QuerySELECT (mysql_res,"can not get the courses a user belongs to",
		      "SELECT crs_users.CrsCod,"	// row[0]
			     "crs_users.Role,"		// row[1]
			     "crs_courses.DegCod"	// row[2]
		       " FROM crs_users,"
			     "crs_courses"
		      " WHERE crs_users.UsrCod=%ld"
		        " AND crs_users.CrsCod=crs_courses.CrsCod"
		        " AND crs_courses.DegCod=%ld"
		   " ORDER BY crs_courses.ShortName",
		      UsrCod,
		      PrtCod);
   else			// All the courses
      return (unsigned)
      DB_QuerySELECT (mysql_res,"can not get the courses a user belongs to",
		      "SELECT crs_users.CrsCod,"	// row[0]
			     "crs_users.Role,"		// row[1]
			     "crs_courses.DegCod"	// row[2]
		       " FROM crs_users,"
			     "crs_courses,"
			     "deg_degrees"
		      " WHERE crs_users.UsrCod=%ld"
		        " AND crs_users.CrsCod=crs_courses.CrsCod"
		        " AND crs_courses.DegCod=deg_degrees.DegCod"
		   " ORDER BY deg_degrees.ShortName,"
			     "crs_courses.ShortName",
		      UsrCod);
  }

/*****************************************************************************/
/************* Change the institutional course code of a course **************/
/*****************************************************************************/

void Crs_DB_UpdateInstitutionalCrsCod (long HieCod,const char *NewInstitutionalCrsCod)
  {
   DB_QueryUPDATE ("can not update the institutional code of a course",
		   "UPDATE crs_courses"
		     " SET InsCrsCod='%s'"
		   " WHERE CrsCod=%ld",
                   NewInstitutionalCrsCod,
                   HieCod);
  }

/*****************************************************************************/
/****************** Change the year/semester of a course *********************/
/*****************************************************************************/

void Crs_DB_UpdateCrsYear (long HieCod,unsigned NewYear)
  {
   DB_QueryUPDATE ("can not update the year of a course",
		   "UPDATE crs_courses"
		     " SET Year=%u"
		   " WHERE CrsCod=%ld",
	           NewYear,
	           HieCod);
  }

/*****************************************************************************/
/***************** Update course name in table of courses ********************/
/*****************************************************************************/

void Crs_DB_UpdateCrsName (long HieCod,
			   const char *FldName,const char *NewCrsName)
  {
   DB_QueryUPDATE ("can not update the name of a course",
		   "UPDATE crs_courses"
		     " SET %s='%s'"
		   " WHERE CrsCod=%ld",
	           FldName,NewCrsName,
	           HieCod);
  }

/*****************************************************************************/
/********************** Update degree in table of courses ********************/
/*****************************************************************************/

void Crs_DB_UpdateDegOfCurrentCrs (long HieCod)
  {
   DB_QueryUPDATE ("can not move course to another degree",
		   "UPDATE crs_courses"
		     " SET DegCod=%ld"
		   " WHERE CrsCod=%ld",
	           HieCod,
	           Gbl.Hierarchy.Node[Hie_CRS].HieCod);
  }

/*****************************************************************************/
/********************** Change the status of a course ************************/
/*****************************************************************************/

void Crs_DB_UpdateCrsStatus (long HieCod,Hie_Status_t Status)
  {
   DB_QueryUPDATE ("can not update the status of a course",
		   "UPDATE crs_courses"
		     " SET Status=%u"
		   " WHERE CrsCod=%ld",
                   (unsigned) Status,
                   HieCod);
  }

/*****************************************************************************/
/***************** Update my last click in current course ********************/
/*****************************************************************************/

void Crs_DB_UpdateCrsLastClick (void)
  {
   /***** Update last access to current course *****/
   DB_QueryREPLACE ("can not update last access to current course",
		    "REPLACE INTO crs_last"
		    " (CrsCod,LastTime)"
		    " VALUES"
		    " (%ld,NOW())",
		    Gbl.Hierarchy.Node[Hie_CRS].HieCod);
  }

/*****************************************************************************/
/******************** Remove information about a course **********************/
/*****************************************************************************/

void Crs_DB_RemoveCrsInfo (long HieCod)
  {
   /* Remove information source of the course */
   DB_QueryDELETE ("can not remove info sources of a course",
		   "DELETE FROM crs_info_src"
		   " WHERE CrsCod=%ld",
		   HieCod);

   /* Remove information text of the course */
   DB_QueryDELETE ("can not remove info of a course",
		   "DELETE FROM crs_info_txt"
		   " WHERE CrsCod=%ld",
		   HieCod);
  }

/*****************************************************************************/
/********************** Remove timetable of a course *************************/
/*****************************************************************************/

void Crs_DB_RemoveCrsTimetable (long HieCod)
  {
   DB_QueryDELETE ("can not remove the timetable of a course",
		   "DELETE FROM tmt_courses"
		   " WHERE CrsCod=%ld",
		   HieCod);
  }

/*****************************************************************************/
/***** Remove course from table of last accesses to courses in database ******/
/*****************************************************************************/

void Crs_DB_RemoveCrsLast (long HieCod)
  {
   DB_QueryDELETE ("can not remove a course",
		   "DELETE FROM crs_last"
		   " WHERE CrsCod=%ld",
		   HieCod);
  }

/*****************************************************************************/
/************* Remove course from table of courses in database ***************/
/*****************************************************************************/

void Crs_DB_RemoveCrs (long HieCod)
  {
   DB_QueryDELETE ("can not remove a course",
		   "DELETE FROM crs_courses"
		   " WHERE CrsCod=%ld",
		   HieCod);
  }
