/*
 * 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.domain;

import hu.astrid.debgen.patientmanagement.domain.modules.Result;
import hu.astrid.debgen.patientmanagement.domain.modules.requests.AnalysisRequest;
import hu.astrid.debgen.exception.ValidationFailedException;
import hu.astrid.debgen.patientmanagement.domain.modules.DebGenModule;
import hu.astrid.toucan.control.bean.ToucanBean;
import hu.astrid.toucan.servlet.httprequesttoobjectparser.HttpRequestToObjectParser;
import hu.astrid.xmltools.beans.XmlConvertable;
import hu.astrid.xmltools.beantoxml.XmlToBeanConverter;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.servlet.http.HttpServletRequest;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * The class of patients
 * @author inagy
 */
@Entity
@Table(name = "debgen_patients")
public class Patient implements ToucanBean, Serializable, XmlConvertable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private int patientType;
    //only anonymous
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private AnonymousData anonymousData;
    //only named
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private PersonalIdentity identity;
    //both
    @Temporal(TemporalType.DATE)
    private Date dateOfBirth;
    private String patientCode;
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "patient")
    private Set<AnalysisRequest> analysisRequests = new TreeSet<AnalysisRequest>();
    @Transient
    private int copies = 1;
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "patient")
    protected Set<Result> results = new TreeSet<Result>();

    public Set<DebGenModule> getModules() {
        TreeSet<DebGenModule> modules = new TreeSet<DebGenModule>();
//        modules.addAll(analysisRequests);
        modules.addAll(results);
        return modules;
    }

    public Set<AnalysisRequest> getUnboundAnalysisRequests() {
        TreeSet<AnalysisRequest> free = new TreeSet<AnalysisRequest>();
        for (AnalysisRequest a : analysisRequests) {
            if (a.getResults().isEmpty()) {
                free.add(a);
            }
        }
        return free;
    }

    public Set<DebGenModule> getRequests() {
        TreeSet<DebGenModule> modules = new TreeSet<DebGenModule>();
        modules.addAll(analysisRequests);
//        modules.addAll(results);
        return modules;
    }

    // <editor-fold defaultstate="collapsed" desc="Getters and Setters">
    /**
     * Get the value of results
     *
     * @return the value of results
     */
    public Set<Result> getResults() {
        return results;
    }

    /**
     * Set the value of results
     *
     * @param results new value of results
     */
    public void setResults(Set<Result> results) {
        this.results = results;
    }

    /**
     * Get the value of copies
     *
     * @return the value of copies
     */
    public int getCopies() {
        return copies;
    }

    /**
     * Set the value of copies
     *
     * @param copies new value of copies
     */
    public void setCopies(int copies) {
        this.copies = copies;
    }

    /**
     * Get the value of analysisRequests
     *
     * @return the value of analysisRequests
     */
    public Set<AnalysisRequest> getAnalysisRequests() {
        return analysisRequests;
    }

    public List<AnalysisRequest> getAnalysisList() {
        List<AnalysisRequest> list = new ArrayList<AnalysisRequest>(analysisRequests);
        Collections.sort(list);
        return list;
    }

    /**
     * Set the value of analysisRequests
     *
     * @param analysisRequests new value of analysisRequests
     */
    public void setAnalysisRequests(Set<AnalysisRequest> analysisRequests) {
        this.analysisRequests = analysisRequests;
    }

    /**
     * Get the value of anonymous
     *
     * @return the value of anonymous
     */
    public AnonymousData getAnonymousData() {
        return anonymousData;
    }

    /**
     * Set the value of anonymous
     *
     * @param anonymousData new value of anonymousData
     */
    public void setAnonymousData(AnonymousData anonymousData) {
        this.anonymousData = anonymousData;
    }

    /**
     * Get the value of identity
     *
     * @return the value of identity
     */
    public PersonalIdentity getIdentity() {
        return identity;
    }

    /**
     * Set the value of identity
     *
     * @param identity new value of identity
     */
    public void setIdentity(PersonalIdentity identity) {
        this.identity = identity;
    }

    /**
     * Get the value of patientCode
     *
     * @return the value of patientCode
     */
    public String getPatientCode() {
        return patientCode;
    }

    /**
     * Set the value of patientCode
     *
     * @param patientCode new value of patientCode
     */
    public void setPatientCode(String patientCode) {
        this.patientCode = patientCode;
    }

    /**
     * Get the value of dateOfBirth
     *
     * @return the value of dateOfBirth
     */
    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public String getDateOfBirthForWeb() {
        try {
            return new SimpleDateFormat("yyyy. MM. dd.").format(dateOfBirth);
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * Set the value of dateOfBirth
     *
     * @param dateOfBirth new value of dateOfBirth
     */
    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    /**
     * Returns true if the patient is anonymous
     * @return true if the patient is anonymous
     */
    public boolean isAnonymous() {
        return (this.patientType & 1) > 0;
    }

    /**
     * Returns true if the patient is named
     * @return true if the patient is named
     */
    public boolean isNamed() {
        return (this.patientType & 2) > 0;
    }

    /**
     * Returns true if the patient is named and anonymous
     * @return true if the patient is named and anonymous
     */
    public boolean isNamedAndAnonymous() {
        return (this.patientType == 3);
    }

    /**
     * Get the value of patientType
     *
     * @return the value of patientType
     */
    public int getPatientType() {
        return patientType;
    }

    /**
     * Set the value of patientType
     *
     * @param patientType new value of patientType
     */
    public void setPatientType(int patientType) {
        this.patientType = patientType;
    }

    /**
     * Get the value of id
     *
     * @return the value of id
     */
    public Long getId() {
        return id;
    }

    /**
     * Set the value of id
     *
     * @param id new value of id
     */
    public void setId(Long id) {
        this.id = id;
    }// </editor-fold>

    public void convert(HttpServletRequest request) throws Exception {
        HttpRequestToObjectParser parser = HttpRequestToObjectParser.getInstance(request);
        parser.parseRequest(Patient.class, this);
    }

    /**
     * Validate the current patients datas
     * @return True if the current patient (this) is valid
     * @throws ValidationFailedException When the patient is not valid
     */
    public boolean validate() throws ValidationFailedException {
        ArrayList<String> properties = new ArrayList<String>(5);
        if (this.isAnonymous() && !this.isNamed() && (this.anonymousData == null || this.anonymousData.getMonogram() == null || this.anonymousData.getMonogram().equals(""))) {
            properties.add("MONOGRAM_");
        }
        if (this.isNamed() && (this.identity == null || this.identity.getPatientName() == null || this.identity.getPatientName().equals(""))) {
            properties.add("NÉV_");
        }
        if (this.getDateOfBirthForWeb().equals("")) {
            properties.add("SZÜL_IDŐ");
        }
        if (this.isNamed() && (this.identity.getTajNumber() == null || this.identity.getTajNumber().length() != 9 || !this.identity.getTajNumber().matches("[0-9]{9,9}"))) {
            properties.add("TAJ");
        }
        if (properties.isEmpty()) {
            return true;
        } else {
            throw new ValidationFailedException(properties);
        }
    }

    @Override
    public String toString() {
        return "[Patient instance: Code: " + patientCode + "| DBRowID:" + id + "]";
    }

    public void buildFromXml(Node node) throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void buildFromXml(Node node, String nameSpace) throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Node toXml(Document doc) throws Exception {
        return toXml(doc, "");
    }

    public Node toXml(Document doc, String nameSpace) throws Exception {
        String prefix = nameSpace == null || nameSpace.isEmpty() ? "" : nameSpace + ":";
        Element patient = doc.createElement(prefix + "patient");
        /*
         *
         * TODO: Ha a beteg mindenben részt vesz és aki nyomtat, kutató, akkor is
         * a 3-as kerül be az xml-be és látszani fognak a beteg adatai a papíron!
         *
         */
        patient.setAttribute("objectType", "" + this.patientType);
        Node common = doc.createElement(prefix + "commonData");
        patient.appendChild(common);
        Element pCode = doc.createElement(prefix + "patientCode");
        pCode.setAttribute("copies", "" + this.copies);
        pCode.appendChild(doc.createTextNode(patientCode));
        common.appendChild(pCode);
        common.appendChild(XmlToBeanConverter.getNodeWithValue(doc, prefix + "dateOfBirth", new SimpleDateFormat("yyyy-MM-dd").format(dateOfBirth)));
        if (this.isNamed()) {
            patient.appendChild(this.identity.toXml(doc, nameSpace));
        } else {
            patient.appendChild(this.anonymousData.toXml(doc, nameSpace));
        }
        return patient;
    }

    public XmlToBeanConverter getXmlConverter() throws Exception {
        return new XmlToBeanConverter();
    }

    public void addAnalysis(AnalysisRequest analysis) {
        this.analysisRequests.add(analysis);
    }

    /**
     * Returns the JSON representation of this object
     * @return The JSON representation of this object
     */
    public String toJSON() {
        StringBuilder sb = new StringBuilder();
        sb.append("{ ");
        sb.append("\"id\": \"" + this.id + "\", \n");
        sb.append("\"research\": \"" + this.isAnonymous() + "\", \n");
        sb.append("\"diagnostical\": \"" + this.isNamed() + "\", \n");
        sb.append("\"patientCode\": \"" + this.patientCode + "\", \n");
        sb.append("\"dateOfBirth\": \"" + new SimpleDateFormat("yyyy. MM. dd.").format(dateOfBirth) + "\", \n");
        sb.append("\"dateOfBirthForJS\": \"" + dateOfBirth.getTime() + "\"");
        if (this.isNamed()) {
            sb.append(", \n\"personalIdentity\": " + this.identity.toJSON() + " ");
        }
        if (this.isAnonymous()) {
            sb.append(", \n\"anonymousData\": " + this.anonymousData.toJSON() + " ");
        }
        sb.append(" \n}");
        return sb.toString();
    }

    public void addResult(Result result) {
        this.results.add(result);
    }
}
