package com.ibm.examplehealth;

import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.stream.JsonCollectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.PathParam;
import java.io.StringReader;
import java.lang.StringBuffer;
import java.sql.ResultSet;
import java.util.List;
import java.util.logging.*;

// [x] GET /getInfo/patients/{patID} - gets patient’s details
// [x] GET /getInfo/prescription/{patID} - gets patient’s prescriptions
// POST /healthInfo/addPatient - adds patient to db. Needed?
// [x] GET /listObs/{patID} - gets patient’s observations
// [x] POST /login/user - patient login
// GET /login/userID/{uID}/pwd/{pass} - patient login. Needed?
// POST /appointments/create - adds appt for patient Needed?
// GET /appointments/list/{patID} - gets patient’s appointments
// [x] GET /countCities - gets population of cities in db
// [x] GET /showAllergies - gets city allergy data
// [x] PUT /generate - creates and populates database
// GET /getInfo/patients - gets all patients in db
// GET /listDiseases - gets diseases of patients in db
// GET /getInfo/prescription - gets counts of patient prescriptions

@Path("/")
@Stateless
public class ExampleResource {

    @PersistenceContext
    EntityManager entityManager;
    int batchSize = 100;

    static Logger logger = Logger.getLogger("ExampleHealthAPI");
    static {
        logger.setLevel(Level.ALL);
        logger.addHandler(new ConsoleHandler());
    }

    @GET
    @Path("/v1/countCities")
    @Produces(MediaType.APPLICATION_JSON)
    public Response countCities() {

        List<CityCounts> results = entityManager.createNamedQuery("Patient.getPop", CityCounts.class).getResultList();
        /* Output format
        "ResultSet Output": [
            {
                "CITY": "Akron",
                "POSTCODE": "44223",
                "NUM_IN_CITY": 13
            }],
            "StatusCode": 200,
            "StatusDescription": "Execution Successful”
            } 
        */

        Jsonb jsonb = JsonbBuilder.create();
        String cityBlob = "{\"ResultSet Output\": " + jsonb.toJson(results)
        + ", \"StatusCode\": 200, \n \"StatusDescription\": \"Execution Successful\"}";

        JsonReader jsonReader = Json.createReader(new StringReader(cityBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();

        return Response.ok(jresponse).build();
	}

    @GET
    @Path("/v1/showAllergies")
    @Produces(MediaType.APPLICATION_JSON)
    public Response showAllergies() {
        /*
        "ResultSet Output": [
            {
                "CITY": "Albany              ",
                "POSTCODE": "12202     ",
                "PATIENT_NUM": 1437,
                "BIRTHDATE": "1961-11-26",
                "ALLERGY_START": "1991-10-08",
                "ALLERGY_STOP": null,
                "DESCRIPTION": "Allergy to fish"
            } ],
        "StatusCode": 200,
        "StatusDescription": "Execution Successful"
    }     
    */   


    // select Patients.patient_id, Patients.birthdate, Patients.city, Patients.postcode, Allergies.description, Allergies.allergy_start, Allergies.allergy_stop from Patients JOIN Allergies ON Patients.patient_id = Allergies.patient_id;

        List<AllergyList> results = entityManager.createNamedQuery("Allergy.getAllergies", AllergyList.class).getResultList();

        Jsonb jsonb = JsonbBuilder.create();
        String allergyBlob = "{\"ResultSet Output\": " + jsonb.toJson(results)
        + ", \"StatusCode\": 200, \n \"StatusDescription\": \"Execution Successful\"}";

        JsonReader jsonReader = Json.createReader(new StringReader(allergyBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();

        return Response.ok(jresponse).build();
	}

    @GET
    @Path("/v1/getInfo/patients")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPatients() {
        List<Patient> results = entityManager.createNamedQuery("Patient.getPatients", Patient.class).getResultList();
        Jsonb jsonb = JsonbBuilder.create();
        String patientBlob = "{\"ResultSet Output\": " + jsonb.toJson(results)
        + ", \"StatusCode\": 200, \n \"StatusDescription\": \"Execution Successful\"}";
        
        JsonReader jsonReader = Json.createReader(new StringReader(patientBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();
		return Response.ok(jresponse).build();
    }

	@GET
    @Path("/v1/getInfo/patients/{patId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPatient(@PathParam("patId") String patId) {
        List<Patient> results = entityManager.createNamedQuery("Patient.findPatient", Patient.class)
                .setParameter("pid", patId)
                .getResultList();
        Jsonb jsonb = JsonbBuilder.create();
        logger.info("Found this many patients with id " + patId + " = " + results.size());
        int returnCode = 0;
        if (results.size() == 0) {
            returnCode=1;
            // return Response.ok("No patients found.").build();
        }

        String patientBlob = "{\"HCCMAREA\": {" + 
        " \"CA_REQUEST_ID\" : \"01IPAT\"," + 
        " \"CA_RETURN_CODE\": " + returnCode + "," + 
        " \"CA_PATIENT_ID\": \"" + patId + "\"," + 
        " \"CA_PATIENT_REQUEST\": " + (returnCode==0 ? jsonb.toJson(results.get(0)) : "\"\"") +
        "}}";

        logger.info("Patient blob: " + patientBlob);

        JsonReader jsonReader = Json.createReader(new StringReader(patientBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();
		return Response.ok(jresponse).build();
	}

    @GET
    @Path("/v1/appointments/list/{patId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response appointments(@PathParam("patId") String patId) {
        List<AppointmentList> results = entityManager.createNamedQuery("Appointment.getAppointments", AppointmentList.class)
                .setParameter("pid", patId)
                .getResultList();
        Jsonb jsonb = JsonbBuilder.create();

        
        String appointmentBlob = "{\"ResultSet Output\": " + jsonb.toJson(results)
        + ", \"StatusCode\": 200, \n \"StatusDescription\": \"Execution Successful\"}";

        int returnCode = 0;
        if (results.size() == 0) {
            returnCode=1;
        }
        
        logger.info("Appointment blob: " + appointmentBlob);

        JsonReader jsonReader = Json.createReader(new StringReader(appointmentBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();
		return Response.ok(jresponse).build();
	}

    @GET
    @Path("/v1/getInfo/prescription/{patId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPrescriptions(@PathParam("patId") String patId) {
        List<Prescription> results = entityManager.createNamedQuery("Prescription.getPrescription", Prescription.class)
                .setParameter("pid", patId)
                .getResultList();
        Jsonb jsonb = JsonbBuilder.create();
        logger.info("Found this many prescriptions with id " + patId + " = " + results.size());
        int returnCode = 0;
        if (results.size() == 0) {
            returnCode=1;
            // return Response.ok("No prescriptions found.").build();
        }

        String prescriptionBlob = "{\"GETMEDO\": {" + 
        " \"CA_REQUEST_ID\" : \"01IPAT\"," + 
        " \"CA_RETURN_CODE\": " + returnCode + "," + 
        " \"CA_PATIENT_ID\": \"" + patId + "\"," + 
        " \"CA_LIST_MEDICATION_REQUEST\": { \"CA_MEDICATIONS\": " + jsonb.toJson(results) + "}}}";

        logger.info("Prescription blob: " + prescriptionBlob);

        JsonReader jsonReader = Json.createReader(new StringReader(prescriptionBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();
		return Response.ok(jresponse).build();
    }

    @GET
    @Path("/v1/getInfo/prescription")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPrescriptionsCount() {

        List<Object[]> results = entityManager.createNamedQuery("Prescription.countScripts").getResultList();
        
        StringBuffer sb = new StringBuffer();

        sb.append("[");
        for (Object[] o : results) {
            sb.append("{\"DRUG_NAME\":\"" + o[0] + "\", \"TOTAL_PATIENTS\":\"" + o[1] + "\"},");
        }
        sb.deleteCharAt(sb.lastIndexOf(","));
        sb.append("]");

        return Response.ok(sb.toString()).build();
    }

    @GET
    @Path("/v1/listObs/{patId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response listObs(@PathParam("patId") String patId) {
        List<Observation> results = entityManager.createNamedQuery("Observation.getObservations", Observation.class)
                .setParameter("pid", patId)
                .getResultList();
        Jsonb jsonb = JsonbBuilder.create();
        logger.info("Found this many observations with id " + patId + " = " + results.size());
        int returnCode = 0;
        if (results.size() == 0) {
            returnCode=1;
        }
     
/* output
        Format of JSON output:

        "ResultSet Output": [
            {
                "PATIENTID": 1,
                "DATEOFOBSERVATION": "2018-05-03",
                "CODE": "11111-0 ",
                "DESCRIPTION": "Tobacco smoking status NHIS",
                "NUMERICVALUE": null,
                "CHARACTERVALUE": "Former smoker",
                "UNITS": null
            }],
        "StatusCode": 200,
        "StatusDescription": "Execution Successful"
*/        
        String observationBlob = "{\"ResultSet Output\": " + jsonb.toJson(results)
        + ", \"StatusCode\": 200, \n \"StatusDescription\": \"Execution Successful\"}";

        logger.info("Observation blob: " + observationBlob);

        JsonReader jsonReader = Json.createReader(new StringReader(observationBlob));
            
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();
		return Response.ok(jresponse).build();
        
    }

    @GET
    @Path("/v1/listDiseases")
    @Produces(MediaType.APPLICATION_JSON)
    public Response listDiseases() {

        long asthma = entityManager.createNamedQuery("Prescription.countAsthma", Long.class).getSingleResult();
        long diabetes = entityManager.createNamedQuery("Prescription.countDiabetes", Long.class).getSingleResult();
        long ptnt_cnt = entityManager.createNamedQuery("Patient.countAll", Long.class).getSingleResult();

        String returnBlob = "{"
        + "\"PATIENTS\": " + ptnt_cnt + ","
        + "\"DIABETES\": " + diabetes + ","
        + "\"ASTHMA\": " + asthma
        + "}";

        return Response.ok(returnBlob).build();
    }

    @POST
    @Path("/v1/login/user")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response login(String body) {
        // {"UID":username,"PASS":password}
         Jsonb jsonb = JsonbBuilder.create();
         Credentials c = jsonb.fromJson(body, Credentials.class);
        List<Patient> results = entityManager.createNamedQuery("Patient.login", Patient.class)
                .setParameter("userId", c.UID)
                .setParameter("password", c.PASS)
                .getResultList();
        logger.info("Found this many patients: " + results.size());
        int returnCode = 0;
        if (results.size() == 0) {
            returnCode=1;
        }

        /*
            "ResultSet Output": [
            {
                "PATIENTID": 1
            }
            ],
        "StatusCode": 200,
        "StatusDescription": "Execution Successful"
    }*/
        if (returnCode==1) {
            return Response.status(Status.NOT_FOUND).build();
        }
        
        String loginBlob = "{\"ResultSet Output\":" + jsonb.toJson(results) 
        + ", \"StatusCode\": 200, \n \"StatusDescription\": \"Execution Successful\"}";

        logger.info("login blob: " + loginBlob);
        JsonReader jsonReader = Json.createReader(new StringReader(loginBlob));
        JsonObject jresponse  = jsonReader.readObject();
        jsonReader.close();
		return Response.ok(jresponse).build();         
    }

    @POST
	@Produces(MediaType.TEXT_PLAIN)
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/v1/generate")
    public String generate(String synthData) {
        Jsonb jsonb = JsonbBuilder.create();
        SynthData synData = jsonb.fromJson(synthData, SynthData.class);
        int cnt=0;

        logger.info("Loading " + synData.patients.size() + " patient records.");
        for (Patient ptnt : synData.patients) {
            cnt++;
            String patientFirstName = ptnt.getFirstName().replaceAll("[0-9]", "");
            String patientLastName = ptnt.getLastName().replaceAll("[0-9]", "");
            ptnt.setFirstName(patientFirstName);
            ptnt.setLastName(patientLastName);
            // logger.info("First name: " + ptnt.getFirstName());
            String patientCredential = ptnt.getFirstName().replaceAll("[0-9]", "").toLowerCase() + ptnt.getLastName().replaceAll("[0-9]", "").toLowerCase().charAt(0);
            ptnt.setPassword(patientCredential);
            ptnt.setUserId(patientCredential);
            entityManager.persist(ptnt);
            flushBatch(synData.patients.size(), cnt, "Patients");
        }
        cnt = 0;

        logger.info("Loading " + synData.providers.size() + " provider records.");
        for (Provider p : synData.providers) {
            cnt++;
            entityManager.persist(p);
            flushBatch(synData.providers.size(), cnt, "Providers");
        }
        cnt = 0;

        logger.info("Loading " + synData.organizations.size() + " organization records.");
        for (Organization o : synData.organizations) {
            cnt++;
            entityManager.persist(o);
            flushBatch(synData.organizations.size(), cnt, "Organizations");
        }
        cnt=0;

        logger.info("Loading " + synData.allergies.size() + " allergy records.");
        for (Allergy a: synData.allergies) {
            cnt++;
            entityManager.persist(a);
            flushBatch(synData.allergies.size(), cnt, "Allergies");
        }
        cnt=0;

        logger.info("Loading " + synData.medications.size() + " medication records.");
        for (Prescription p : synData.medications) {
            cnt++;
            String[] description = p.getDrugName().replaceAll("(NDA[0-9]+)|(\\.)","").split("[0-9]+");
            p.setDrugName(  description[0].length() > 0 ? description[0] : description[1].trim().substring(description[1].trim().indexOf(" ")) );
            entityManager.persist(p);
            flushBatch(synData.medications.size(), cnt, "Prescriptions");
        }
        cnt=0;

        logger.info("Loading " + synData.encounters.size() + " encounter records.");
        for (Appointment a : synData.encounters) {
            cnt++;
            String datetime = a.getDate();
            a.setDate(datetime.substring(0,10));
            a.setTime(datetime.substring(11,19));
            entityManager.persist(a);
            flushBatch(synData.encounters.size(), cnt, "Encounters");
        }
        cnt=0;

        logger.info("Loading " + synData.observations.size() + " observation records.");
        for (Observation o : synData.observations) {
            cnt++;
            if (o.type.equals("numeric")) {
                o.setNumericValue(o.jsonValue);
            } else {
                o.setCharacterValue(o.jsonValue);
            }
            entityManager.persist(o);
            flushBatch(synData.observations.size(), cnt, "Observations");
        }

        return new String("Loaded : " + (synData.observations.size() +
                                        synData.organizations.size() +
                                        synData.providers.size() +
                                        synData.encounters.size() +
                                        synData.medications.size() +
                                        synData.patients.size() +
                                        synData.allergies.size()) + " records.");
    }

    @POST
	@Produces(MediaType.TEXT_PLAIN)
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/v1/testAddPatient")
    public void testAddPatient(Patient ptnt) {
        String patientFirstName = ptnt.getFirstName().replaceAll("[0-9]", "");
        String patientLastName = ptnt.getLastName().replaceAll("[0-9]", "");
        ptnt.setFirstName(patientFirstName);
        ptnt.setLastName(patientLastName);
        logger.info("First name: " + ptnt.getFirstName());
        String patientCredential = ptnt.getFirstName().replaceAll("[0-9]", "").toLowerCase() + ptnt.getLastName().replaceAll("[0-9]", "").toLowerCase().charAt(0);
        ptnt.setPassword(patientCredential);
        ptnt.setUserId(patientCredential);
		entityManager.persist(ptnt);
    }

    private void flushBatch(int size, int cnt, String type) {
        if ( (cnt % batchSize == 0) || (size == cnt) )  {
            logger.info((size - cnt) + " " + type + " remaining.");
            entityManager.flush();
            entityManager.clear();
        }
    }

}