/*
Copyright 2023, Arani Chakravarti

This file is part of Homoeopim.

Homoeopim 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 any later
version.

Homoeopim 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 Homoeopim. If not, see <https://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <time.h>
#include <cmath>

#include <myclips/clips.h>
#include <libpq-fe.h>

#include "pookrandom.h"

#ifndef BEGIN
#define BEGIN(i) {
#define END(i) }
#endif

#define beta 0.1 // 1/kT for the metropolis weights 

using namespace std;

typedef struct {
int remedy_id;
float score;
float metropolis_weight;
} rem_score;

///////////////////////////////////

class clips
{
private:

public:

//---------------------------------

clips() // constructor
{
InitializeEnvironment();
}

//---------------------------------

inline int CL_Load(char *c) { return(Load(c)); }

inline void CL_AssertString(char *c) { AssertString(c); }

inline void CL_Facts() { Facts((char*)"wdisplay",NULL,-1,-1,-1); }

inline void CL_Build(char *c) { Build(c); }

inline void CL_Reset() { Reset(); }

inline void CL_Run() { Run(-1L); }

//----------------------------------

// This is our own function - not in the original lib
inline void CL_get_global_value(char *name,char *retvalue,int retvalue_length)
{
GetDefglobalValueForm(retvalue,retvalue_length,FindDefglobal(name));
}

//----------------------------------

// friend declarations!

friend void InitializeEnvironment();

}; // end of class clips

/////////////////////////////////////

class chikitsha : public clips, public pookrandom

BEGIN(0)

private:

char facts[1000][200];

int num_facts;
char fact_name[20];
char sql_command[300];

rem_score remedy_score[1500];

int num_relevant_remedies;
int selected_serial_number, selected_remedy_id;

int patient_age;
char patient_gender;
char notes[300];


PGconn *conn;
PGresult *res,*res1,*res2;

int user_id, otp;
int num_bytes_written;

public:

//--------------------------------

chikitsha() // Constructor

BEGIN(chititsha-0)

user_id = atoi(getenv("WWW_user_id")); // get user id
char connection_auth[100];
sprintf(connection_auth,"host=localhost port=5433 dbname=homoeopim user=homoeopim password=%s",getenv("HOMOEOPIM_DATABASE_PASSWORD"));
conn = PQconnectdb(connection_auth);
num_bytes_written=snprintf(sql_command,299,"select gender,year_of_birth from patient where id=%d",user_id);
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
patient_gender = PQgetvalue(res,0,0)[0]; // save gender
time_t t = time(NULL);
struct tm tm = *localtime(&t);
patient_age = tm.tm_year +1900 - atoi(PQgetvalue(res,0,1)); // save age

num_bytes_written=snprintf(sql_command,299,"select type,significant_text from patient_details where patient_id = %d",user_id);
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
num_facts = PQntuples(res); // get number of facts
for(int i=0;i<num_facts;++i)
BEGIN(chikitsha-1)
sprintf(facts[i],"(%s %s)",PQgetvalue(res,i,0),PQgetvalue(res,i,1)); // form the facts strings
END(chikitsha-1)


END(chikitsha-0) // end of constructor

//--------------------------------

~chikitsha() // Destructor
{ 
PQfinish(conn);
}

//-------------------------------

void assert_facts_list()

BEGIN(assert_facts_list-0)

for(int i=0;i<num_facts;++i)
BEGIN(assert_facts_list-1)
CL_AssertString(facts[i]);
END(assert_facts_list-1)

END(assert_facts_list-0)

//--------------------------------

void sort_scores() // form a sorted list of remedy scores

BEGIN(sort_scores-0)

char retvalue[100];
char score_variable_name[100];
char dummy[100]; // keeps unused first part of name

num_bytes_written=snprintf(sql_command,299,"select id from remedy order by id");
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
int no_of_remedies = PQntuples(res); // find no. of remedies

for(int i = 0; i < no_of_remedies; ++i) // read in remedy scores
BEGIN(sort_score-1)
sprintf(score_variable_name,"score_remedy_id_%s",PQgetvalue(res,i,0));
CL_get_global_value(score_variable_name,retvalue,100);
sscanf(retvalue,"%[^=]= %f",dummy,&remedy_score[i].score);
remedy_score[i].remedy_id = atoi(PQgetvalue(res,i,0));
remedy_score[i].metropolis_weight = exp(remedy_score[i].score*beta);
END(sort_score-1)

//simple bubble sort 

int start_remedy_num = 0;
int max_iii = start_remedy_num;;
rem_score max,tmp;
while(start_remedy_num < no_of_remedies)
BEGIN(sort_score-2)
max = remedy_score[start_remedy_num]; // will this simple copy work?
for(int iii=start_remedy_num+1;iii<(no_of_remedies-1);++iii)
BEGIN(sort_score-3)
if(max.score < remedy_score[iii].score)
BEGIN(sort_score-4)
max = remedy_score[iii];
max_iii = iii;
END(sort_score-4)
END(sort_score-3) // maximum has been found
// interchange to place max at the top
tmp = remedy_score[start_remedy_num];
remedy_score[start_remedy_num] = max;
remedy_score[max_iii] = tmp;
++start_remedy_num;
END(sort_score-2)

num_relevant_remedies = 0;
for(int i=0;(i<no_of_remedies)&&(remedy_score[i].score>0);++i)
{
num_relevant_remedies++; // here we get the number of relevant remedies
}
//cout << "Number of relevant remedies = " << num_relevant_remedies;

END(sort_scores-0)

//--------------------------------

void select_target_remedy()

BEGIN(select_target_remedy-0)

float total_score = 0;

for(int i=0;i<num_relevant_remedies;++i)
{
total_score += remedy_score[i].metropolis_weight;
}
//cout << "<br>Total score is : " << total_score;
float this_rand = scaled_rand(0,total_score);
//cout << "<br>this_rand is : " << this_rand;

float start_score = 0, end_score;

for(int i=0;i<num_relevant_remedies;++i)

BEGIN(select_target_remedy-1)

end_score = start_score+remedy_score[i].metropolis_weight; // end of the metropolis range
if((this_rand >= start_score) && (this_rand < end_score))

BEGIN(select_target_remedy-2)

selected_serial_number = i;
selected_remedy_id = remedy_score[selected_serial_number].remedy_id;
goto escape; // not very comfortable with break!

END(select_target_remedy-2)

else

BEGIN(select_target_remedy-3)

start_score = end_score;

END(select_target_remedy-3)
END(select_target_remedy-1)

escape: num_bytes_written=snprintf(sql_command,299,"select remedy from remedy where id=%d",selected_remedy_id); // lucky remedy name!
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command); // error check to be included
//cout << "<br>Selected remedy: "<<PQgetvalue(res,0,0)<<", id = "<<selected_remedy_id<<", serial number = "<< selected_serial_number;

END(select_target_remedy-0)

//--------------------------------

void select_question()

BEGIN(select_question-0)

int num_hit,target_hit_number;

//cout << "<br>Patient age : "<<patient_age<< " gondar: "<<patient_gender;
int symptom_type = pookrand()%4; // This will have to be increased to 5
				   // to include ordinary symptoms


switch(symptom_type)

BEGIN(select_question-1)

case(0): // am
//cout << "<br>Amelioration!!";
num_bytes_written=snprintf(sql_command,299,"select amelioration_id,notes from amelioration_remedy_map where remedy_id = %d",selected_remedy_id);
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
num_hit = PQntuples(res);
//cout << "<br>num_hit = "<<num_hit;


if(num_hit != 0)

BEGIN(select_question-2)

target_hit_number = pookrand()%num_hit;

num_bytes_written=snprintf(sql_command,299,"select parameter,gender,age_dependence,start_age,end_age from amelioration where id = %s and clips_lhs like '(am %%'",PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
//cout << "<br>sql_command - " << sql_command;
res1 = PQexec(conn,sql_command);
if(PQntuples(res1) == 1)
{
if(check_suitability(atoi(PQgetvalue(res,target_hit_number,0)),(char*)"am",user_id,PQgetvalue(res1,0,1)[0],PQgetvalue(res1,0,2)[0],atoi(PQgetvalue(res1,0,3)),atoi(PQgetvalue(res1,0,4))) == 1)
{

// Display query
if(strlen(PQgetvalue(res,target_hit_number,1)) == 0)
{ notes[0] = '\0'; } // empty string
else // create notes string
{
num_bytes_written = snprintf(notes,299," (%s)",PQgetvalue(res,target_hit_number,1));
notes[num_bytes_written] = '\0';
}
cout << "<tr><td>amelioration</td><td>"<<PQgetvalue(res1,0,0)<<notes<<"</td><td align=center><input type=checkbox name=feedback value=\"am "<<PQgetvalue(res,target_hit_number,0)<<" "<<PQgetvalue(res1,0,0)<<"\"</td></tr>\n";

// Make entry in asked_questions table
num_bytes_written = snprintf(sql_command,299,"insert into asked_questions (patient_id,type,symptom_id) values (%d,'am',%s)",user_id,PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
PQexec(conn,sql_command);
}
}

END(select_question-3)

break;

case(1): // amt
//cout << "<br>Amelioration time!";
num_bytes_written=snprintf(sql_command,299,"select amelioration_id,notes from amelioration_remedy_map where remedy_id = %d",selected_remedy_id);
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
num_hit = PQntuples(res);
//cout << "<br>num_hit = "<<num_hit;

if(num_hit != 0)

BEGIN(select_question-3)

target_hit_number = pookrand()%num_hit;

num_bytes_written=snprintf(sql_command,299,"select parameter,gender,age_dependence,start_age,end_age from amelioration where id = %s and clips_lhs like '(amt %%'",PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
//cout << "<br>sql_command - " << sql_command;
res1 = PQexec(conn,sql_command);
if(PQntuples(res1) == 1)
{
if(check_suitability(atoi(PQgetvalue(res,target_hit_number,0)),(char*)"amt",user_id,PQgetvalue(res,0,1)[0],PQgetvalue(res1,0,2)[0],atoi(PQgetvalue(res1,0,3)),atoi(PQgetvalue(res1,0,4))) == 1)
{
// Display query
if(strlen(PQgetvalue(res,target_hit_number,1)) == 0)
{ notes[0] = '\0'; } // empty string
else // create notes string
{
num_bytes_written = snprintf(notes,299," (%s)",PQgetvalue(res,target_hit_number,1));
notes[num_bytes_written] = '\0';
}
cout << "<tr><td>amelioration-time</td><td>"<<PQgetvalue(res1,0,0)<<notes<<"</td><td align=center><input type=checkbox name=feedback value=\"amt "<<PQgetvalue(res,target_hit_number,0)<<" "<<PQgetvalue(res1,0,0)<<"\"</td></tr>\n";

// Make entry in asked_questions table
num_bytes_written = snprintf(sql_command,299,"insert into asked_questions (patient_id,type,symptom_id) values (%d,'amt',%s)",user_id,PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
PQexec(conn,sql_command);
}
}

END(select_question-3)

break;

case(2): // ag
//cout << "<br>Aggravation!";
num_bytes_written=snprintf(sql_command,299,"select aggravation_id,notes from aggravation_remedy_map where remedy_id = %d",selected_remedy_id);
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
num_hit = PQntuples(res);
//cout << "<br>num_hit = "<<num_hit;

if(num_hit != 0)

BEGIN(select_question-4)

target_hit_number = pookrand()%num_hit;

num_bytes_written=snprintf(sql_command,299,"select parameter,gender,age_dependence,start_age,end_age from aggravation where id = %s and clips_lhs like '(ag %%'",PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
//cout << "<br>sql_command - " << sql_command;
res1 = PQexec(conn,sql_command);
if(PQntuples(res1) == 1)
{
if(check_suitability(atoi(PQgetvalue(res,target_hit_number,0)),(char*)"ag",user_id,PQgetvalue(res1,0,1)[0],PQgetvalue(res1,0,2)[0],atoi(PQgetvalue(res1,0,3)),atoi(PQgetvalue(res1,0,4))) == 1)
{
// Display query
if(strlen(PQgetvalue(res,target_hit_number,1)) == 0)
{ notes[0] = '\0'; } // empty string
else // create notes string
{
num_bytes_written = snprintf(notes,299," (%s)",PQgetvalue(res,target_hit_number,1));
notes[num_bytes_written] = '\0';
}
cout << "<tr><td>aggravation</td><td>"<<PQgetvalue(res1,0,0)<<notes<<"</td><td align=center><input type=checkbox name=feedback value=\"ag "<<PQgetvalue(res,target_hit_number,0)<<" "<<PQgetvalue(res1,0,0)<<"\"</td></tr>\n";

// Make entry in asked_questions table
num_bytes_written = snprintf(sql_command,299,"insert into asked_questions (patient_id,type,symptom_id) values (%d,'ag',%s)",user_id,PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
PQexec(conn,sql_command);
}
}

END(select_question-4)

break;

case(3): // agt
//cout << "<br>Aggravation time!";
num_bytes_written=snprintf(sql_command,299,"select aggravation_id,notes from aggravation_remedy_map where remedy_id = %d",selected_remedy_id);
sql_command[num_bytes_written] = '\0';
res = PQexec(conn,sql_command);
num_hit = PQntuples(res);
//cout << "<br>num_hit = "<<num_hit;

if(num_hit != 0)

BEGIN(select_question-5)

target_hit_number = pookrand()%num_hit;

num_bytes_written=snprintf(sql_command,299,"select parameter,gender,age_dependence,start_age,end_age from aggravation where id = %s and clips_lhs like '(agt %%'",PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
//cout << "<br>sql_command - " << sql_command;
res1 = PQexec(conn,sql_command);
if(PQntuples(res1) == 1)
{
if(check_suitability(atoi(PQgetvalue(res,target_hit_number,0)),(char*)"agt",user_id,PQgetvalue(res1,0,1)[0],PQgetvalue(res1,0,2)[0],atoi(PQgetvalue(res1,0,3)),atoi(PQgetvalue(res1,0,4))) == 1)
{
// Display query
if(strlen(PQgetvalue(res,target_hit_number,1)) == 0)
{ notes[0] = '\0'; } // empty string
else // create notes string
{
num_bytes_written = snprintf(notes,299," (%s)",PQgetvalue(res,target_hit_number,1));
notes[num_bytes_written] = '\0';
}
cout << "<tr><td>aggravation-time</td><td>"<<PQgetvalue(res1,0,0)<<notes<<"</td><td align=center><input type=checkbox name=feedback value=\"agt "<<PQgetvalue(res,target_hit_number,0)<<" "<<PQgetvalue(res1,0,0)<<"\"</td></tr>\n";
}

// Make entry in asked_questions table
num_bytes_written = snprintf(sql_command,299,"insert into asked_questions (patient_id,type,symptom_id) values (%d,'agt',%s)",user_id,PQgetvalue(res,target_hit_number,0));
sql_command[num_bytes_written] = '\0';
PQexec(conn,sql_command);
}

END(select_question-5)

break;

default:
cout << "<tr><td colspan=3><font color=red>hai! e ki holo?</font></td></tr>\n";

END(select_question-1)
END(select_question-0)

//--------------------------------

int check_suitability(int symptom_id,char *symptom_type,int user_id,char gender,char age_dependence,int start_age,int end_age)

BEGIN(check_suitability-0)

//cout << "<tr><td colspan=3>symptom_id:" << symptom_id << " symptom_type:"<<symptom_type<<" user_id:"<<user_id<<" gender:"<<gender<<" age_dependence:"<< age_dependence<<" start_age:"<<start_age<<" end_age:"<<end_age<<"</td></tr>";
if(strcmp(symptom_type,(char*)"am") == 0)

BEGIN(check_suitability-1)

if((patient_gender != gender)&&(gender != 'b')) // gender check
{ return 0; }

if((age_dependence == 'y') && !((patient_age >= start_age)&&(patient_age < end_age))) // age check
{ cout<<"<tr><td colspan=3>Age!</td></tr>";return 0; }

// Check whether already asked
num_bytes_written = snprintf(sql_command,299,"select * from asked_questions where patient_id = %d and type ='am' and symptom_id = %d",user_id,symptom_id);
sql_command[num_bytes_written] = '\0';
//cout<<"<tr><td colspan=3>Symptom_id = "<<symptom_id<<"</td></tr>";
res2 = PQexec(conn,sql_command);
if(PQntuples(res2) != 0)
{ return 0; }

return 1; // checks passed!

END(check_suitability-1)

else if(strcmp(symptom_type,(char*)"amt") == 0)

BEGIN(check_suitability-2)

if((patient_gender != gender)&&(gender != 'b')) // gender check
{ return 0; }

if((age_dependence == 'y') && !((patient_age >= start_age)&&(patient_age < end_age))) // age check
{ return 0; }

// Check whether already asked
num_bytes_written = snprintf(sql_command,299,"select * from asked_questions where patient_id = %d and type ='amt' and symptom_id = %d",user_id,symptom_id);
sql_command[num_bytes_written] = '\0';
res2 = PQexec(conn,sql_command);
if(PQntuples(res2) != 0)
{ return 0; }

return 1; // checks passed!

END(check_suitability-2)

else if(strcmp(symptom_type,(char*)"ag") == 0)

BEGIN(check_suitability-3)

if((patient_gender != gender)&&(gender != 'b')) // gender check
{ return 0; }

if((age_dependence == 'y') && !((patient_age >= start_age)&&(patient_age < end_age))) // age check
{ return 0; }

// Check whether already asked
num_bytes_written = snprintf(sql_command,299,"select * from asked_questions where patient_id = %d and type ='ag' and symptom_id = %d",user_id,symptom_id);
sql_command[num_bytes_written] = '\0';
res2 = PQexec(conn,sql_command);
if(PQntuples(res2) != 0)
{ return 0; }

return 1; // checks passed!

END(check_suitability-3)

else if(strcmp(symptom_type,(char*)"agt") == 0)

BEGIN(check_suitability-4)

if((patient_gender != gender)&&(gender != 'b')) // gender check
{ return 0; }

if((age_dependence == 'y') && !((patient_age >= start_age)&&(patient_age < end_age))) // age check
{ return 0; }

// Check whether already asked
num_bytes_written = snprintf(sql_command,299,"select * from asked_questions where patient_id = %d and type ='agt' and symptom_id = %d",user_id,symptom_id);
sql_command[num_bytes_written] = '\0';
res2 = PQexec(conn,sql_command);
if(PQntuples(res2) != 0)
{ return 0; }

END(check_suitability-4)

return 1; // checks passed!

END(check_suitability-0)

//--------------------------------

void try_questions()
{
cout << "<form action=/cgi-bin/uncgi/homoeopim/chikitsha_submit_feedback.cgi method=post>\n";

cout << "<input type=hidden name=user_id value="<<user_id<<">\n";
cout << "<input type=hidden name=otp value="<<otp<<">\n";

cout << "<table border>\n"; // form the feedback table
cout << "<tr><td align=center><b><i><font color=red>Type</font></i></b></td><td align=center><b><i><font color=red>Parameter/Query</font></i></b></td><td align=centre><b><i><font color=red>Choose</font></i></b></td></tr>\n";

for(int i=0;i<20;++i)
{
select_target_remedy();
select_question();
}
cout << "<tr><td colspan=3 align=center><input type=submit name=submit value=\"Submit feedback\"></td></tr>\n";
cout << "</table>\n";
cout << "</form>\n";
}

//-------------------------------

END(0);

////////////////////////////////////

int main(void)

BEGIN(main-0)

cout << "Content-type: text/html\n\n";

chikitsha daktaar;

// Evaluation

cout << "<html>\n";
cout << "<head>\n";
cout << "<link rel=\"stylesheet\" href=\"/homoeopim/homoeopim.css\">\n";
cout << "</head>\n";
cout << "<body>\n";
daktaar.CL_Load((char*)"/usr/lib/cgi-bin/homoeopim/chikitsha.clp"); // load clips file
daktaar.CL_Reset(); // allows deffacts to be loaded
daktaar.assert_facts_list(); // assert facts
daktaar.CL_Run(); // run clips to form remedy scores
cout << "<div align=center>\n";
cout << "<h3><font color=DarkOliveGreen>Please supply feedback on the following:</font></h3>";
cout << "</div>\n";
daktaar.sort_scores(); // remedies in descending order of scores
daktaar.try_questions();
cout << "</body></html>\n";
END(main-0)
