/*
 * This file is part of DebGen Biobank Software developed by ASTRID Research.
 * All rights reserved. Reproduction, modification, use or disclosure
 * to third parties without express authority is forbidden.
 *
 * Copyright ASTRID Research Inc., Hungary, 2009-2011.
 *
 * ASTRID Research
 * Headquaters: Csapo utca 42. Debrecen H-4029 HUNGARY
 * Phone: +36-52-533-173        Fax: +36-52-533-174
 * Web: http://www.astrid.hu    E-mail: info@astrid.hu
 *
 * Project name: DebGen Biobank Software project.
 * URLS:
 * Developers: Istvan Zoltan Nagy (istvan.nagy@astrid.hu)
 */
package hu.astrid.debgen.patientmanagement.service;

import hu.astrid.debgen.patientmanagement.domain.AnonymousData;
import hu.astrid.debgen.patientmanagement.domain.Patient;
import hu.astrid.debgen.patientmanagement.domain.PersonalIdentity;
import hu.astrid.shared.server.service.BaseOperationsAbstract;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

/**
 * The Service, which is handling the patients
 * @author inagy
 */
public class PatientManagerService extends BaseOperationsAbstract {

    /**
     * Returns an instance of a PatientManagerService and sets its session.
     * @param session The session os the Service to use the DB connection.
     * @return A new instance.
     */
    public static BaseOperationsAbstract getInstance(Session session) {
        PatientManagerService patientManagerService = new PatientManagerService();
        patientManagerService.session = session;
        return patientManagerService;
    }

    /**
     * Loads the patient by the given id
     * @param id The id of the patient
     * @return The patient object with the specified id (if there is such thing)
     */
    public Patient load(Long id) {
        Patient patient = super.load(Patient.class, id);
        Hibernate.initialize(patient.getAnalysisRequests());
        Hibernate.initialize(patient.getResults());
        return patient;
    }

    /**
     * Returns a list of anonymous patients
     * @param order The ordering attribute to order the results ascending
     * @param offset The index of the first result entry.
     * @param length The number of elements on one page
     * @param asc Order ascending
     * @return The list of records on the current page.
     */
    public List<Patient> list(String order, int offset, int length, boolean asc) {
        return this.find(null, order, offset, length, asc);
    }

    /**
     * Finds the matching anonymous patient(s) using the criterias
     * @param what The search criterias
     * @param order The ordering attribute to order the results ascending
     * @param offset The index of the first result entry.
     * @param length The number of elements on one page
     * @param asc Order ascending
     * @return The list of records on the current page.
     */
    public List<Patient> find(Patient what, String order, int offset, int length, boolean asc) {
        return this.find(what, order, offset, length, false, asc);
    }

    /**
     * Returns a list of anonymous and/or named patients
     * @param order The ordering attribute to order the results ascending
     * @param offset The index of the first result entry.
     * @param length The number of elements on one page
     * @param asc Order ascending
     * @return The list of records on the current page.
     */
    public List<Patient> listAll(String order, int offset, int length, boolean asc) {
        return this.findAll(null, order, offset, length, asc);
    }

    /**
     * Finds the matching anonymous and/or named patient(s) using the criterias
     * @param what The search criterias
     * @param order The ordering attribute to order the results ascending
     * @param offset The index of the first result entry.
     * @param length The number of elements on one page
     * @param asc Order ascending
     * @return The list of records on the current page.
     */
    public List<Patient> findAll(Patient what, String order, int offset, int length, boolean asc) {
        return this.find(what, order, offset, length, true, asc);
    }

    /**
     * Finds the matching anonymous and/or named patient(s) using the criterias
     * @param what The search criterias
     * @param order The ordering attribute to order the results ascending
     * @param offset The index of the first result entry.
     * @param length The number of elements on one page
     * @param named Is this search for named persons too?
     * @param asc Order ascending
     * @return The list of records on the current page.
     */
    public List<Patient> find(Patient what, String order, int offset, int length, boolean named, boolean asc) {
        boolean pNM = false;
        if (order.equals("patientNameMonogram")) {
            order = "identity.patientName";
            pNM = true;
        } else if (order.equals("taj")) {
            order = "identity.tajNumber";
        } else if (order.equals("monogram")) {
            order = "anonymousData.monogram";
        }
        Criteria criteria = session.createCriteria(Patient.class);
        criteria = criteria.createAlias("identity", "identity", CriteriaSpecification.LEFT_JOIN);
        criteria = criteria.createAlias("anonymousData", "anonymousData", CriteriaSpecification.LEFT_JOIN);
        if (what != null) {
            if (what.isNamed() && what.getIdentity() != null) {
                /*    if (what.getIdentity().getPatientName() != null && !what.getIdentity().getPatientName().equals("")) {
                criteria = criteria.add(Restrictions.like("identity.patientName", what.getIdentity().getPatientName(), MatchMode.ANYWHERE));
                }
                if (what.getIdentity().getTajNumber() != null && !what.getIdentity().getTajNumber().equals("")) {
                criteria = criteria.add(Restrictions.like("identity.tajNumber", what.getIdentity().getTajNumber(), MatchMode.ANYWHERE));
                }
                }
                if (what.getPatientCode() != null && !what.getPatientCode().equals("")) {
                criteria = criteria.add(Restrictions.like("patientCode", what.getPatientCode(), MatchMode.ANYWHERE));*/

                criteria = criteria.add(Restrictions.or(Restrictions.or(
                        Restrictions.like("identity.patientName", what.getIdentity().getPatientName(), MatchMode.START),
                        Restrictions.like("identity.tajNumber", what.getIdentity().getPatientName(), MatchMode.START)),
                        Restrictions.like("patientCode", what.getIdentity().getPatientName(), MatchMode.START)));
            }
        }
        if (!named) {
            criteria = criteria.add(Restrictions.ne("patientType", 2));
        }
        if (asc) {
            criteria = criteria.addOrder(Order.asc(order));
            if (pNM) {
                criteria = criteria.addOrder(Order.asc("anonymousData.monogram"));
            }
        } else {
            criteria = criteria.addOrder(Order.desc(order));
            if (pNM) {
                criteria = criteria.addOrder(Order.desc("anonymousData.monogram"));
            }
        }
        List list = criteria.setFirstResult(offset).setMaxResults(length).list();
        List<Patient> patients = new ArrayList<Patient>();
        if (list != null && !list.isEmpty()) {
            for (Object o : list) {
                patients.add((Patient) o);
            }
        }
        if (patients.size() == 1) {
            Hibernate.initialize(patients.get(0).getAnalysisRequests());
            Hibernate.initialize(patients.get(0).getResults());
        }
        return patients;
    }

    /**
     * Deletes the given patient objects record from the DB
     * @param patient The patient which should be deleted
     */
    public void delete(Patient patient) {
        session.delete(patient);
    }

    /**
     * Deletes the given anonymousData objects record from the DB
     * @param ad The anonymousData which should be deleted
     */
    public void delete(AnonymousData ad) {
        session.delete(ad);
    }

    /**
     * Deletes the given PersonalIdentity objects record from the DB
     * @param ad The PersonalIdentity which should be deleted
     */
    public void delete(PersonalIdentity id) {
        session.delete(id);
    }

    /**
     * Returns the number of matching patient records
     * @param all Include all patients?
     * @return The number of matching patients
     */
    public double countList(boolean all) {
        return this.countSearchList(null, all);
    }

    /**
     * Returns the number of matching patient records
     * @param what The search criterias.
     * @param all Include all patients?
     * @return The number of matching patients
     */
    public double countSearchList(Patient what, boolean all) {
        Criteria criteria = session.createCriteria(Patient.class);
        if (what != null) {
            if (what.isNamed() && what.getIdentity() != null) {
                criteria = criteria.createAlias("identity", "identity", CriteriaSpecification.LEFT_JOIN);
                /*if (what.getIdentity().getPatientName() != null && !what.getIdentity().getPatientName().equals("")) {
                criteria = criteria.add(Restrictions.like("identity.patientName", what.getIdentity().getPatientName(), MatchMode.ANYWHERE));
                }
                if (what.getIdentity().getTajNumber() != null && !what.getIdentity().getTajNumber().equals("")) {
                criteria = criteria.add(Restrictions.like("identity.tajNumber", what.getIdentity().getTajNumber(), MatchMode.ANYWHERE));
                }
                }
                if (what.getPatientCode() != null && !what.getPatientCode().equals("")) {
                criteria = criteria.add(Restrictions.like("patientCode", what.getPatientCode(), MatchMode.ANYWHERE));*/

                criteria = criteria.add(Restrictions.or(Restrictions.or(
                        Restrictions.like("identity.patientName", what.getIdentity().getPatientName(), MatchMode.START),
                        Restrictions.like("identity.tajNumber", what.getIdentity().getPatientName(), MatchMode.START)),
                        Restrictions.like("patientCode", what.getIdentity().getPatientName(), MatchMode.START)));
            }
        }
        if (!all) {
            criteria = criteria.add(Restrictions.ne("patientType", 2));
        }
        Object result = criteria.setProjection(Projections.countDistinct("id")).uniqueResult();
        return Double.parseDouble(result.toString());
    }

    /**
     * Lists the names of patients matching the pattern
     * @param pattern The pattern that should occure somewhere in the name of the patient
     * @return The list of matching patient names
     */
    public List<String> listNames(String pattern) {
        List list = session.createCriteria(PersonalIdentity.class).add(Restrictions.like("patientName", pattern, MatchMode.START)).addOrder(Order.asc("patientName")).list();
        ArrayList<String> result = new ArrayList<String>();
        for (Object o : list) {
            String patientName = ((PersonalIdentity) o).getPatientName();
            if (!result.contains(patientName)) {
                result.add(patientName);
            }
        }
        return result;
    }

    /**
     * Lists the TAJ numbers of patients matching the pattern
     * @param pattern The pattern that should occure somewhere in the TAJ of the patient
     * @return The list of matching patient TAJ numbers
     */
    public List<String> listTAJ(String pattern) {
        List list = session.createCriteria(PersonalIdentity.class).add(Restrictions.like("tajNumber", pattern, MatchMode.START)).addOrder(Order.asc("tajNumber")).list();
        ArrayList<String> result = new ArrayList<String>();
        for (Object o : list) {
            String tajNumber = ((PersonalIdentity) o).getTajNumber();
            if (!result.contains(tajNumber)) {
                result.add(tajNumber);
            }
        }
        return result;
    }

    /**
     * Lists the BiobankCodes of patients matching the pattern
     * @param pattern The pattern that should occure somewhere in the code of the patient
     * @param all The answer of the "Search all of patients (anonymous and/or named)?" question
     * @return The list of matching patient BiobankCodes
     */
    public List<String> listCode(String pattern, boolean all) {
//        Criteria criteria = session.createCriteria(Patient.class).add(Restrictions.like("patientCode", pattern, MatchMode.START));
//        if (!all) {
//            criteria = criteria.add(Restrictions.ne("patientType", 2));
//        }
//        List list = criteria.addOrder(Order.asc("patientCode")).list();
//        ArrayList<String> result = new ArrayList<String>();
//        for (Object o : list) {
//            result.add(((Patient) o).getPatientCode());
//        }
        return this.listField("patientCode", pattern);
    }

    /**
     * Lists all data of the given String type attribute from the Patient class
     * @param field The name of the field.
     * @param query The pattern which should match in START MatchMode
     * @return The list of matching attribute values
     */
    public List<String> listField(String field, String query) {
        Criteria criteria = session.createCriteria(Patient.class).add(Restrictions.like(field, query, MatchMode.START));
        List list = criteria.addOrder(Order.asc(field)).list();
        ArrayList<String> result = new ArrayList<String>();
        for (Object o : list) {
            try {
                String value = (String) Patient.class.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1)).invoke((Patient) o);
                if (!result.contains(value)) {
                    result.add(value);
                }
            } catch (IllegalAccessException ex) {
                //IGNORED
            } catch (IllegalArgumentException ex) {
                //IGNORED
            } catch (InvocationTargetException ex) {
                //IGNORED
            } catch (NoSuchMethodException ex) {
                //IGNORED
            } catch (SecurityException ex) {
                //IGNORED
            }
        }
        return result;
    }

    /**
     * Finds a person by taj
     * @param taj The TAJ of the patient
     * @return The patient object with the matching TAJ
     */
    public Patient findByTaj(String taj) {
        Criteria criteria = session.createCriteria(Patient.class);
        criteria = criteria.createAlias("identity", "identity", CriteriaSpecification.LEFT_JOIN);
        criteria = criteria.add(Restrictions.like("identity.tajNumber", taj, MatchMode.EXACT));
        Patient patient = (Patient) criteria.uniqueResult();
        Hibernate.initialize(patient.getAnalysisRequests());
        Hibernate.initialize(patient.getResults());
        return patient;
    }

    /**
     * Finds a person by name
     * @param name The Name of the patient
     * @return The patient object with the matching Name
     */
    public String findByName(String name) {
        Criteria criteria = session.createCriteria(Patient.class);
        criteria = criteria.createAlias("identity", "identity", CriteriaSpecification.LEFT_JOIN);
        criteria = criteria.add(Restrictions.like("identity.patientName", name, MatchMode.EXACT)).addOrder(Order.asc("identity.tajNumber"));
        List list = criteria.list();
        StringBuilder sb = new StringBuilder("");
        if (list != null) {
            sb.append("{ \"list\": [ ");
            boolean first = true;
            for (Object o : list) {
                if (!first) {
                    sb.append(", ");
                } else {
                    first = false;
                }
                sb.append("{ \"text\": \"" + ((Patient) o).getIdentity().getTajNumber() + "\", \"taj\": \"" + ((Patient) o).getIdentity().getTajNumber() + "\" }");
            }
            sb.append("], \"size\": \"" + list.size() + "\" } ");
        }
        return sb.toString();
    }
}
