/*
 * 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.Patient;
import hu.astrid.toucan.control.persistence.DatabaseServiceClass;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;

/**
 * Service to generate unique patient codes before the patient save process
 * @author inagy
 */
public class PatientCodeGeneratorService extends DatabaseServiceClass {

    /**
     * The length of the patient code
     */
    private static final int codeLENGTH = 6;
    /**
     * The size of the pool which contains fresh, unused codes
     */
    private static final int FRESH_code_POOLSIZE = 100;
    /**
     * The length of the timeout period of a code in seconds if the code isn't saved to the DB
     */
    private static final int TIMEOUT_SECONDS = 3600;
    /**
     * Logger
     */
    private static Logger logger = Logger.getLogger(PatientCodeGeneratorService.class);
    /**
     * The charset of the non digit parts of the code
     */
    private static final String CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    /**
     * The list of "garbage" codes. The garbage codes where timed out one or more times.
     */
    private static LinkedList<String> garbage = new LinkedList<String>();
    /**
     * The pool of fresh codes, waiting to save them
     */
    private static HashMap<String, Date> fresh = new HashMap<String, Date>();

    /**
     * Checks the availability of a code
     * @param code The code that should be checked
     * @return True if this code isn't saved to the DB
     * @throws Exception When DB fails...
     */
    private static boolean checkAvailability(String code) throws Exception {
        logger.debug("checkAvailibility() called.");
        if (code == null || code.length() != codeLENGTH) {
            return false;
        }
        PatientCodeGeneratorService service = new PatientCodeGeneratorService();
        Session session = service.startSession();
        try {
            session.beginTransaction();
            List list = session.createCriteria(Patient.class).add(Restrictions.eq("patientCode", code)).list();
            session.getTransaction().commit();
            return (list == null || list.isEmpty());
        } catch (Exception ex) {
            service.rollback();
            throw ex;
        } finally {
            service.closeSession();
        }
    }

    /**
     * Generates and returns a new patient code
     * @return The new code
     * @throws Exception When the generation process fails
     */
    public static String generateCode() throws Exception {
        logger.debug("generateCode() called.");
        StringBuilder sb = null;
        String result = null;
        if (garbage.isEmpty()) {
            do {
                sb = new StringBuilder();
                sb.append("D");
                for (int i = 0; i < (codeLENGTH - 3); i++) {
                    int random = (int) (Math.random() * CHARSET.length());
                    sb.append(CHARSET.charAt(random));
                }
                sb.append(new SimpleDateFormat("yy").format(new Date()));
            } while (!checkAvailability(sb.toString()) || fresh.containsKey(sb.toString()));
            fresh.put(sb.toString(), new Date());
            result = sb.toString();
        } else {
            result = garbage.pop();
            if (result.endsWith(new SimpleDateFormat("yy").format(new Date()))) {
                //ha nem változott az év, akkor minden ok
                fresh.put(result, new Date());
            } else {
                /*
                 * ha valaki év végén nem mentett el egy objektumot,
                 * majd köv év elején megkapná a betegkódot, akkor törölni kell
                 * a pool tartalmát
                 */
                garbage.clear();
                result = generateCode();
            }
        }
        if (fresh.size() > FRESH_code_POOLSIZE) {
            logger.debug("Objects in pool before cleanup: " + fresh.size());
            cleanUp();
        }
        return result;
    }

    /**
     * Cleans up the values of the fresh code set, by removing the unused and timed-out codes
     */
    public static void cleanUp() {
        logger.debug("cleanUp() called.");
        for (String code : fresh.keySet()) {
            if (fresh.get(code).getTime() + TIMEOUT_SECONDS * 1000 < new Date().getTime()) {
                garbage.add(code);
            }
        }
        for (String s : garbage) {
            fresh.remove(s);
        }
        logger.debug("Objects in pool after cleanup: " + fresh.size());
    }

    /**
     * Removes the given code from the fresh list (used when a patient with this code where saved)
     * @param code The code of the saved patient
     * @return True if the remove process was successful and false otherwise
     */
    public static boolean removeFromFresh(String code) {
        logger.debug("removeFromFresh() called.");
        return (fresh.remove(code) != null);
    }
}
