/* *********************************
  * HeatControl network interface *
  * Author: Mateusz Viste         *
  *********************************

Change log:
  [18 Oct 2014]
    - fixed a slot-selection bug
    - fixed segfaults when data files do not exist
  [26 Dec 2013]
    - rewritten in ANSI C.
  [15 Nov 2011]
    - Changed the input method for driving data, now heatinterface
      is looking into the QUERY_STRING variable (before it was
      expecting to be launched by the HTTP server with correct
      parameters, but it had some troubles with Apache...). The
      QUERY_STRING method seems to be much more reliable.
  [12 Nov 2009]
    - Added a thin (2px) column between each day,
    - Added version information in the program's output,
    - Added a popup (title) with the timeslot on each line.
  [11 Nov 2009]
    - Added support for a 4th zone.
  [04 Oct 2009]
    - First public release.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define HomepageURL "http://heatcontrol.sourceforge.net/"
#define pDate "18 Oct 2014"


enum useractions {
 SLOTACTIVATE = 1,
 FORCEZONE = 2,
 MODIFY = 3,
 VIEWSLOT = 4
};


struct progdata {
 int WeekProgram[4][7][96];
 int ActiveSlot;
 int SelectedSlot;
 int userparams[8];
 char SlotName[10][128];
 time_t ForcedTime[4];
 int ForcedZone[4];
 int CurZoneState[4];
};


void LoadSlotInfo(struct progdata *pdata) {
 int x, y;
 char tempstring[1024];
 FILE *fd;
 /* load slot names */
 fd = fopen("/etc/heatcontrol/slotnames.cfg", "r");
 if (fd != NULL) {
   for (x = 0; x < 10; x++) {
     fgets(tempstring, 1024, fd);
     if (tempstring[0] != 0) {
         y = strlen(tempstring) - 1;
         if (tempstring[y] == '\n') tempstring[y] = 0; /* trim the trailing LF, if present */
         sprintf(pdata->SlotName[x], "%s", tempstring);
       } else {
         sprintf(pdata->SlotName[x], "Slot %d", x + 1);
     }
   }
   fclose(fd);
 }
 /* load active slot */
 fd = fopen("/etc/heatcontrol/active.cfg", "r");
 if (fd != NULL) {
   fscanf(fd, "%d", &(pdata->ActiveSlot));
   fclose(fd);
 }
}


void LoadForcedInfos(struct progdata *pdata) {
 FILE *fd;
 int x;
 fd = fopen("/etc/heatcontrol/forcing", "r");
 if (fd == NULL) return;
 for (x = 0; x < 4; x++) fscanf(fd, "%d", &(pdata->ForcedZone[x]));
 for (x = 0; x < 4; x++) fscanf(fd, "%ld", &(pdata->ForcedTime[x]));
 fclose(fd);
}


void LoadCurState(struct progdata *pdata) {
 FILE *fd;
 int x;
 fd = fopen("/etc/heatcontrol/curstate.dat", "r");
 if (fd == NULL) return;
 for (x = 0; x < 4; x++) {
   fscanf(fd, "%d", &(pdata->CurZoneState[x]));
 }
 fclose(fd);
}


void LoadWeekProgram(struct progdata *pdata) {
 FILE *fd;
 int x, y, z;
 char filename[256];
 sprintf(filename, "/etc/heatcontrol/%d.cfg", pdata->SelectedSlot);
 fd = fopen(filename, "r");
 if (fd == NULL) return;
 for (x = 0; x < 4; x++) {       /* Zones 1-3 */
   for (y = 0; y < 7; y++) {     /* Weekdays 1-7 */
     for (z = 0; z < 96; z++) {  /* Time spaces 1-96 (24h) */
       fscanf(fd, "%d", &(pdata->WeekProgram[x][y][z]));
     }
   }
 }
 fclose(fd);
}


void LoadUserVariables(struct progdata *pdata) {
 /* heatinterface is parsing the QUERY_STRING environnement variable, and dispatches the content
    of this variable to the following list of local variables:
    $UserAction $UserParam1 $UserParam2 $UserParam3 $UserParam4 $UserParam5

    Then, the program will execute whatever orders we got into our user variables.
    Eg.
     modify 0 1 2 96 2 (set the program of slot 0, zone 1, tuesday, 23h45 to "eco")
     slot 3 (display the slot 3)
     slotactivate 7 (activate the slot 7)
     forcezone 1 3 2 15 (force zone 3 into HorsGel mode for 15 minutes) */
 int x, y, z;
 char *SrvSideParams;
 char tempstring[256] = {0};
 SrvSideParams = getenv("QUERY_STRING");
 if (SrvSideParams == NULL) return;
 y = 0;
 z = 0;
 for (x = 0; ; x++) {
   if ((SrvSideParams[x] != '&') && (SrvSideParams[x] != ';') && (SrvSideParams[x] != 0)) {
       tempstring[z] = SrvSideParams[x];
       if (z < 63) z++;
     } else {
       tempstring[z] = 0;
       z = 0;
       pdata->userparams[y] = atoi(tempstring);
       tempstring[0] = 0;
       if (y < 7) y++;
       if (SrvSideParams[x] == 0) break;
   }
 }
}


void SaveConfiguration(struct progdata *pdata) {
 FILE *fd;
 char tempstring[8192];
 char *tstring;
 char slotfilename[64];
 int x, y, z;
 tstring = tempstring;
 /* save the currently active slot */
 fd = fopen("/etc/heatcontrol/active.cfg", "w");
 if (fd != NULL) {
   fprintf(fd, "%d\n", pdata->ActiveSlot);
   fclose(fd);
 }
 for (x = 0; x < 4; x++) {      /* Zones 1-4 */
   for (y = 0; y < 7; y++) {    /* Weekdays 1-7 */
     for (z = 0; z < 96; z++) { /* Time spaces 1-96 (24h) */
       tstring += sprintf(tstring, "%d ", pdata->WeekProgram[x][y][z]);
     }
   }
 }
 sprintf(slotfilename, "/etc/heatcontrol/%d.cfg", pdata->SelectedSlot);
 fd = fopen(slotfilename, "w");
 if (fd != NULL) {
   fprintf(fd, "%s\n", tempstring);
   fclose(fd);
 }
}


char *SecondsToString(int seconds) {
 static char Result[8];
 int FullHours, MinutesLeft, SecondsLeft;
 FullHours = seconds / 3600;
 SecondsLeft = seconds % 3600;
 MinutesLeft = SecondsLeft / 60;
 SecondsLeft %= 60;
 if (FullHours > 0) {
     sprintf(Result, "%02dh%02d", FullHours, MinutesLeft);
   } else {
     sprintf(Result, "%02d:%02d", MinutesLeft, SecondsLeft);
 }
 return(Result);
}


/*  * Here starts the real program *  */
/* States are: 0 = OFF ; 1 = HorsGel ; 2 = ECO ; 3 = COMFORT */

int main(void) {
 int x, y, z;
 int Modified = 0;
 char *ScriptURL;
 struct progdata *pdata;
 char *HeatState[4] = {"OFF", "No freeze", "ECO", "COMFORT"};
 char *HeatStateColor[4] = {"#A0A0A0", "#C5C5C5", "#C0F0C0", "#FFC0C0"};
 char *Timespace[96] = {"00h00", "00h15", "00h30", "00h45", "01h00", "01h15", "01h30", "01h45",
                        "02h00", "02h15", "02h30", "02h45", "03h00", "03h15", "03h30", "03h45",
                        "04h00", "04h15", "04h30", "04h45", "05h00", "05h15", "05h30", "05h45",
                        "06h00", "06h15", "06h30", "06h45", "07h00", "07h15", "07h30", "07h45",
                        "08h00", "08h15", "08h30", "08h45", "09h00", "09h15", "09h30", "09h45",
                        "10h00", "10h15", "10h30", "10h45", "11h00", "11h15", "11h30", "11h45",
                        "12h00", "12h15", "12h30", "12h45", "13h00", "13h15", "13h30", "13h45",
                        "14h00", "14h15", "14h30", "14h45", "15h00", "15h15", "15h30", "15h45",
                        "16h00", "16h15", "16h30", "16h45", "17h00", "17h15", "17h30", "17h45",
                        "18h00", "18h15", "18h30", "18h45", "19h00", "19h15", "19h30", "19h45",
                        "20h00", "20h15", "20h30", "20h45", "21h00", "21h15", "21h30", "21h45",
                        "22h00", "22h15", "22h30", "22h45", "23h00", "23h15", "23h30", "23h45"};

 /* first send out the CGI http header */
 puts("Content-Type: text/html; charset=UTF-8");
 puts("");  /* An empty line to notify the CGI parser that the HTTP header ends here */

 pdata = calloc(1, sizeof(*pdata));
 if (pdata == NULL) {
   puts("<html><head><title>Error</title></head><body>FATAL ERROR: OUT OF MEMORY!</body></html>\n");
   return(4);
 }

 ScriptURL = getenv("SCRIPT_NAME");

 LoadUserVariables(pdata);
 LoadSlotInfo(pdata);

 if (pdata->userparams[0] == 0) {   /* If no selected slot, then we assume a 'VIEW' action on active slot */
   pdata->userparams[0] = VIEWSLOT;
   pdata->userparams[1] = pdata->ActiveSlot;
 }
 pdata->SelectedSlot = pdata->userparams[1];

 LoadForcedInfos(pdata);
 LoadWeekProgram(pdata);
 LoadCurState(pdata);

 if (pdata->userparams[0] == SLOTACTIVATE) {
     Modified = 1;
     pdata->ActiveSlot = pdata->userparams[1];
   } else if (pdata->userparams[0] == FORCEZONE) {
     FILE *fd;
     if (pdata->userparams[2] > 4) pdata->userparams[2] = 1;
     if (pdata->userparams[2] < 0) pdata->userparams[2] = 0;
     if (pdata->userparams[3] < 0) pdata->userparams[3] = 0;
     if (pdata->userparams[3] > 3) pdata->userparams[3] = 0;
     if (pdata->userparams[4] < 0) pdata->userparams[4] = 0;
     pdata->ForcedZone[pdata->userparams[2]] = pdata->userparams[3];
     pdata->ForcedTime[pdata->userparams[2]] = time(NULL) + pdata->userparams[4];
     fd = fopen("/etc/heatcontrol/forcing", "w");
     if (fd != NULL) {
       for (x = 0; x < 4; x++) fprintf(fd, "%d ", pdata->ForcedZone[x]);
       for (x = 0; x < 4; x++) fprintf(fd, "%ld ", pdata->ForcedTime[x]);
       fclose(fd);
     }
 }

 if (pdata->userparams[0] == MODIFY) {
   if (pdata->userparams[5] > 3) pdata->userparams[5] = 0;
   Modified = 1;
   pdata->WeekProgram[pdata->userparams[2]][pdata->userparams[3]][pdata->userparams[4]] = pdata->userparams[5];
 }
 if (Modified != 0) SaveConfiguration(pdata);  /* If any modifications have been done, save the whole thing */

 /*  *** Now, let's output all the html mess! ***  */

 puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
 puts("<html>");
 puts("  <head>");
 printf("    <meta http-equiv=\"refresh\" content=\"300; URL=%s?%d&amp;%d\">\n", ScriptURL, VIEWSLOT, pdata->SelectedSlot); /* Refresh every 5 minute */
 puts("    <title>HeatControl</title>");
 puts("  </head>");
 puts("  <body>");
 puts("    <table style=\"width: 100%;\">");
 puts("      <tr>");
 puts("        <td style=\"width: 25%;\"><table style=\"border: 1px solid black; margin-right: auto;\"><tr><td style=\"padding: 0.2em 0.7em 0.2em 0.7em;\"><b>Forced states:</b><br>");

 /* Forcing zones table Start */
 for (x = 0; x < 4; x++) {
   y = pdata->ForcedTime[x] - time(NULL);
   if ((y > 0) && (pdata->ForcedZone[x] <= 3) && (pdata->ForcedZone[x] >= 0)) {
       printf("          Zone %d: <a href=\"%s?%d&amp;%d&amp;%d&amp;%d&amp;%d\">%s</a>", x + 1, ScriptURL, FORCEZONE, pdata->SelectedSlot, x, pdata->ForcedZone[x] + 1, y, HeatState[pdata->ForcedZone[x]]);
       printf(" (%s left) ", SecondsToString(pdata->ForcedTime[x] - time(NULL)));
       printf("<a href=\"%s?%d&amp;%d&amp;%d&amp;%d&amp;%d\">+</a> / ", ScriptURL, FORCEZONE, pdata->SelectedSlot, x, pdata->ForcedZone[x], y + 900);
       printf("<a href=\"%s?%d&amp;%d&amp;%d&amp;%d&amp;%d\">-</a><br>\n", ScriptURL, FORCEZONE, pdata->SelectedSlot, x, pdata->ForcedZone[x], y - 900);
     } else {
       printf("          Zone %d: <a href=\"%s?%d&amp;%d&amp;%d&amp;0&amp;900\">Not forced</a><br>\n", x + 1, ScriptURL, FORCEZONE, pdata->SelectedSlot, x);
   }
 }
 /* Forcing zones table End */

 puts("        </td></tr></table></td>");
 printf("        <td style=\"width: 50%%; font-weight: bold; text-align: center; font-size: 1.5em;\">Home heat controlling system<br><a href=\"%s?%d&amp;%d\" style=\"font-weight: normal; font-size: 0.6em;\">[Refresh]</a></td>\n", ScriptURL, VIEWSLOT, pdata->SelectedSlot);

 printf("        <td style=\"width: 25%%;\"><table style=\"border: 1px solid black; margin-left: auto;\"><tr><td style=\"padding: 0.2em 0.7em 0.2em 0.7em;\"><b>Current state:</b><br>");
 printf("Zone 1: %s<br>Zone 2: %s<br>Zone 3: %s<br>Zone 4: %s</td></tr></table></td>\n", HeatState[pdata->CurZoneState[0]], HeatState[pdata->CurZoneState[1]], HeatState[pdata->CurZoneState[2]], HeatState[pdata->CurZoneState[3]]);

 puts("      </tr>");
 puts("    </table>");
 puts("    <table style=\"border: 1px solid black; border-spacing: 1px; width: 100%; background-color: #E0E0F0; margin-bottom: 0.4em;\">");
 puts("      <tr style=\"text-align: center;\">");
 puts("        <td>Slot list:</td>");
 for (x = 0; x < 10; x++) {
   printf("        <td style=\"");
   if (x == pdata->ActiveSlot) printf("font-weight: bold;");
   if (x == pdata->SelectedSlot) printf("background-color: #F5F5FF;");
   printf("\"><a href=\"%s?%d&amp;%d\">%s</a></td>\n", ScriptURL, VIEWSLOT, x, pdata->SlotName[x]);
 }
 puts("      </tr>");
 puts("    </table>");

 if (pdata->SelectedSlot != pdata->ActiveSlot) {
   printf("    <p style=\"text-align: center; font-weight: bold;\"><a href=\"%s?%d&amp;%d\">Activate this slot</a></p>\n", ScriptURL, SLOTACTIVATE, pdata->SelectedSlot);
 }

 puts("    <table style=\"border: 1px solid black; border-spacing: 1px; width: 100%; background-color: #F8F8FF;\">");
 puts("      <tr style=\"text-align: center; font-weight: bold;\"><td>&nbsp;</td><td colspan=\"4\">Monday</td><td style=\"padding: 2px;\"></td><td colspan=\"4\">Tuesday</td><td style=\"padding: 2px;\"></td><td colspan=\"4\">Wednesday</td><td style=\"padding: 2px;\"></td><td colspan=\"4\">Thursday</td><td style=\"padding: 2px;\"></td><td colspan=\"4\">Friday</td><td style=\"padding: 2px;\"></td><td colspan=\"4\">Saturday</td><td style=\"padding: 2px;\"></td><td colspan=\"4\">Sunday</td></tr>");
 printf("      <tr align=\"center\"><td>&nbsp;</td>");
 for (x = 0; x < 7; x++) {
   printf("<td>Zone&nbsp;1</td><td>Zone&nbsp;2</td><td>Zone&nbsp;3</td><td>Zone&nbsp;4</td>");
   if (x < 6) printf("<td></td>");
 }
 puts("</tr>");
 for (x = 0; x < 96; x++) {
   puts("      <tr>");
   printf("        <td style=\"font-size: 0.9em;\"><a id=\"row%d\">%s</a></td>", x, Timespace[x]);
   for (y = 0; y < 7; y++) {
     for (z = 0; z < 4; z++) {
       printf("<td style=\"font-size: 0.6em; text-align: center; background-color: %s;\">", HeatStateColor[pdata->WeekProgram[z][y][x]]);
       printf("<a href=\"%s?%d&amp;%d&amp;%d&amp;%d&amp;%d&amp;%d", ScriptURL, MODIFY, pdata->SelectedSlot, z, y, x, pdata->WeekProgram[z][y][x] + 1);
       if (x > 20) printf("#row%d", x - 20);
       printf("\" title=\"%s\">%s</a></td>", Timespace[x], HeatState[pdata->WeekProgram[z][y][x]]);
     }
     if (y < 7) printf("<td></td>");
   }
   puts("");
   puts("      </tr>");
 }
 puts("    </table>");
 printf("    <p style=\"text-align: right; Font-size: 0.85em; margin-top: 0.2em;\"><a href=\"%s\">HeatControl</a> [%s]</p>\n", HomepageURL, pDate);
 puts("  </body>");
 puts("</html>");

 return(0);
}