/*
 * Copyright (c) 1997 - 2016
 * Actelion Pharmaceuticals Ltd.
 * Gewerbestrasse 16
 * CH-4123 Allschwil, Switzerland
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the the copyright holder nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Modest v. Korff
 */

package com.actelion.research.chem.io.pdb.parser;

import com.actelion.research.chem.Molecule3D;
import com.actelion.research.chem.io.pdb.mmcif.MMCIFParser;
import com.actelion.research.util.IntArrayComparator;
import com.actelion.research.util.SortedList;

import java.util.*;

/**
 * PDBFileEntry
 * Created by korffmo1 on 20.03.18.
 */
public class PDBFileEntry {
	/**
	 *  description of pdb-file format: http://www.wwpdb.org/documentation/file-format
	 */

    private String classification;

    private String pdbID;

    private Date dateDeposition;

    private String obsolete;

    private String title;

    private String split;

    private String caveat;

    private String compound;

    private String source;

    private String keywords;

    private String model;

    private String resolution;

    private String expdata;
    private String nummdl;
    private String mdltyp;
    private String author;

    private List<String> liRevdat;

    private String sprsde;
    private List<String> liJRNL;

    private HashMap<Integer, String> hmNo_Remark;

    private List<String> liDBRef;

    private List<String> liDBRef1DBRef2;

    private List<String> liSEQADV;

    private List<String> liSEQRES;

    private List<String> liModRes;

    private List<String> liHet;

    private HashMap<String, String> hmId_Name;

    private HashMap<String, String> hmId_Synonyms;

    private HashMap<String, String> hmId_Formula;

    private List<String> liHelix;
    private List<String> liSheet;
    private List<String> liSSBond;
    private List<String> liLink;
    private List<String> liCisPep;

    private HashMap<String, String> hmId_Site;

    private String cryst1;
    private String origX1;
    private String origX2;
    private String origX3;
    private String scale1;
    private String scale2;
    private String scale3;

    private List<String> liMtrix1;
    private List<String> liMtrix2;
    private List<String> liMtrix3;
    
    private List<AtomRecord> atomRecords;

    private SortedList<int[]> templateConnections;
    private ArrayList<String[]> mmcifConnections;

    private String master;

    private boolean end;

    public String getClassification() {
        return classification;
    }

    public void setClassification(String classification) {
        this.classification = classification;
    }

    public String getID() {
        return pdbID;
    }

    public void setID(String pdbID) {
        this.pdbID = pdbID;
    }

    public Date getDateDeposition() {
        return dateDeposition;
    }

    public void setDateDeposition(Date dateDeposition) {
        this.dateDeposition = dateDeposition;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    
    public List<AtomRecord> getAtomRecords() {
        return atomRecords;
    }

    public void setAtoms(List<AtomRecord> atomRecords) {
        this.atomRecords = atomRecords;
    }

    public String getObsolete() {
        return obsolete;
    }

    public void setObsolete(String obsolete) {
        this.obsolete = obsolete;
    }

    public String getSplit() {
        return split;
    }

    public void setSplit(String split) {
        this.split = split;
    }

    public String getCaveat() {
        return caveat;
    }

    public void setCaveat(String caveat) {
        this.caveat = caveat;
    }

    public String getCompound() {
        return compound;
    }

    public void setCompound(String compound) {
        this.compound = compound;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public String getKeywords() {
        return keywords;
    }

    public void setKeywords(String keywords) {
        this.keywords = keywords;
    }

    public String getExpdata() {
        return expdata;
    }

    public void setExpdata(String expdata) {
        this.expdata = expdata;
    }

    public String getNummdl() {
        return nummdl;
    }

    public void setNummdl(String nummdl) {
        this.nummdl = nummdl;
    }

    public String getMdltyp() {
        return mdltyp;
    }

    public void setMdltyp(String mdltyp) {
        this.mdltyp = mdltyp;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getResolution() {
        return resolution;
    }

    public void setResolution(String resolution) {
        this.resolution = resolution;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public List<String> getRevdat() {
        return liRevdat;
    }

    public void setRevdat(List<String> liRevdat) {
        this.liRevdat = liRevdat;
    }

    public String getSprsde() {
        return sprsde;
    }

    public void setSprsde(String sprsde) {
        this.sprsde = sprsde;
    }

    public List<String> getJrnl() {
        return liJRNL;
    }

    public void setJrnl(List<String> jrnl) {
        this.liJRNL = jrnl;
    }

    public String getRemark0() {
        return hmNo_Remark.get(0);
    }

    public String getRemark1() {
        return hmNo_Remark.get(1);
    }

    public String getRemark2() {
        return hmNo_Remark.get(2);
    }

    public String getRemark3() {
        return hmNo_Remark.get(3);
    }

    public void setRemarks(HashMap<Integer, String> hmNo_Remark) {
        this.hmNo_Remark = hmNo_Remark;
    }

    public String getRemark(int n) {
        return hmNo_Remark.get(n);
    }

    public List<Integer> getRemarks(){
        List<Integer> liRemarkNo = new ArrayList<>(hmNo_Remark.keySet());

        Collections.sort(liRemarkNo);

        return liRemarkNo;
    }

    public List<String> getDBRef() {
        return liDBRef;
    }

    public void setDBRef(List<String> liDBRef) {
        this.liDBRef = liDBRef;
    }

    public List<String> getDBRef1DBRef2() {
        return liDBRef1DBRef2;
    }

    public void setDBRef1DBRef2(List<String> liDBRef1DBRef2) {
        this.liDBRef1DBRef2 = liDBRef1DBRef2;
    }

    public List<String> getSEQADV() {
        return liSEQADV;
    }

    public void setSEQADV(List<String> liSEQADV) {
        this.liSEQADV = liSEQADV;
    }

    public List<String> getSEQRES() {
        return liSEQRES;
    }

    public void setSEQRES(List<String> liSEQRES) {
        this.liSEQRES = liSEQRES;
    }

    public List<String> getModRes() {
        return liModRes;
    }

    public void setModRes(List<String> liModRes) {
        this.liModRes = liModRes;
    }

    public List<String> getHet() {
        return liHet;
    }

    public void setHet(List<String> liHet) {
        this.liHet = liHet;
    }

    public HashMap<String, String> getHmId_Name() {
        return hmId_Name;
    }

    public void setHmId_Name(HashMap<String, String> hmId_Name) {
        this.hmId_Name = hmId_Name;
    }

    public List<String> getNameIDs(){
        List<String> li = new ArrayList<>(hmId_Name.keySet());
        Collections.sort(li);
        return li;
    }

    public String getName(String nameId){
        return hmId_Name.get(nameId);
    }

    public void setHmId_Synonyms(HashMap<String, String> hm) {
        this.hmId_Synonyms = hm;
    }

    public List<String> getSynonymIDs(){
        List<String> li = new ArrayList<>(hmId_Synonyms.keySet());
        Collections.sort(li);
        return li;
    }

    public String getSynonyms(String synonymId){
        return hmId_Synonyms.get(synonymId);
    }

    public void setHmId_Formula(HashMap<String, String> hm) {
        this.hmId_Formula = hm;
    }

    public List<String> getFormulaIDs(){
        List<String> li = new ArrayList<>(hmId_Formula.keySet());
        Collections.sort(li);
        return li;
    }

    public String getFormula(String formulaId){
        return hmId_Formula.get(formulaId);
    }

    public List<String> getHelix() {
        return liHelix;
    }

    public void setHelix(List<String> liHelix) {
        this.liHelix = liHelix;
    }

    public List<String> getSheet() {
        return liSheet;
    }

    public void setSheet(List<String> liSheet) {
        this.liSheet = liSheet;
    }

    public List<String> getSSBond() {
        return liSSBond;
    }

    public void setSSBond(List<String> liSSBond) {
        this.liSSBond = liSSBond;
    }

    public List<String> getLink() {
        return liLink;
    }

    public void setLink(List<String> liLink) {
        this.liLink = liLink;
    }

    public List<String> getCisPep() {
        return liCisPep;
    }

    public void setCisPep(List<String> liCisPep) {
        this.liCisPep = liCisPep;
    }

    public void setHmId_Site(HashMap<String, String> hm) {
        this.hmId_Site = hm;
    }

    public List<String> getSiteIDs(){
        List<String> li = new ArrayList<>(hmId_Site.keySet());
        Collections.sort(li);
        return li;
    }

    public String getSite(String id){
        return hmId_Site.get(id);
    }

    public String getCryst1() {
        return cryst1;
    }

    public void setCryst1(String cryst1) {
        this.cryst1 = cryst1;
    }

    public String getOrigX1() {
        return origX1;
    }

    public void setOrigX1(String origX1) {
        this.origX1 = origX1;
    }

    public String getOrigX2() {
        return origX2;
    }

    public void setOrigX2(String origX2) {
        this.origX2 = origX2;
    }

    public String getOrigX3() {
        return origX3;
    }

    public void setOrigX3(String origX3) {
        this.origX3 = origX3;
    }

    public String getScale1() {
        return scale1;
    }

    public void setScale1(String scale1) {
        this.scale1 = scale1;
    }

    public String getScale2() {
        return scale2;
    }

    public void setScale2(String scale2) {
        this.scale2 = scale2;
    }

    public String getScale3() {
        return scale3;
    }

    public void setScale3(String scale3) {
        this.scale3 = scale3;
    }

    public List<String> getMtrix1() {
        return liMtrix1;
    }

    public void setMtrix1(List<String> liMtrix1) {
        this.liMtrix1 = liMtrix1;
    }

    public List<String> getMtrix2() {
        return liMtrix2;
    }

    public void setMtrix2(List<String> liMtrix2) {
        this.liMtrix2 = liMtrix2;
    }

    public List<String> getMtrix3() {
        return liMtrix3;
    }

    public void setMtrix3(List<String> liMtrix3) {
        this.liMtrix3 = liMtrix3;
    }

    public void setTemplateConnections(SortedList<int[]> templateConnections) {
        this.templateConnections = templateConnections;
    }

    public void setNonStandardConnections(ArrayList<String[]> connections) {
        this.mmcifConnections = connections;
    }

    public String getMaster() {
        return master;
    }

    public void setMaster(String master) {
        this.master = master;
    }

    public boolean isEnd() {
        return end;
    }

    public void setEnd(boolean end) {
        this.end = end;
    }
    
    public Map<String,List<Molecule3D>> extractMols() {
        return extractMols(false);
    }

    public Map<String,List<Molecule3D>> extractMols(boolean detachCovalentLigands) {
        if (templateConnections == null)
            templateConnections = new SortedList<>(new IntArrayComparator());

        SortedList<int[]> nonStandardConnections = new SortedList<>(new IntArrayComparator());
        // If we have the data from a mmcif file, then translate connections defined by atom names to global atom indexes
        if (mmcifConnections != null && !mmcifConnections.isEmpty()) {
            TreeMap<String,Integer> atomNameToIDMap = new TreeMap<>();
            for (AtomRecord atom : atomRecords)
                atomNameToIDMap.put(MMCIFParser.atomDescription(atom.getLabelAtomName(), Integer.toString(atom.getLabelSeqID()), atom.getResName(), Integer.toString(atom.getAuthSeqID()), atom.getChainID()), atom.getSerialId());

            for (String[] connection : mmcifConnections) {
                Integer atom1 = atomNameToIDMap.get(connection[0]);
                Integer atom2 = atomNameToIDMap.get(connection[1]);
                if (atom1 == null)
                    System.out.println("Bond atom '"+connection[0]+"' not found");
                else if (atom2 == null)
                    System.out.println("Bond atom '"+connection[1]+"' not found");
                else {
                    int[] bond = new int[2];
                    bond[0] = atom1;
                    bond[1] = atom2;
                    nonStandardConnections.add(bond);
                }
            }
        }

        return new StructureAssembler(templateConnections, nonStandardConnections, atomRecords, detachCovalentLigands).assemble();
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("PDBCoordEntryFile{");
        sb.append("classification='").append(classification).append('\'');
        sb.append(", pdbID'").append(pdbID).append('\'');
        sb.append(", dateDeposition=").append(dateDeposition);
        sb.append(", obsolete='").append(obsolete).append('\'');
        sb.append(", title='").append(title).append('\'');
        sb.append(", split='").append(split).append('\'');
        sb.append(", caveat='").append(caveat).append('\'');
        sb.append(", compound='").append(compound).append('\'');
        sb.append(", source='").append(source).append('\'');
        sb.append(", keywords='").append(keywords).append('\'');
        sb.append(", expdata='").append(expdata).append('\'');
        sb.append(", nummdl='").append(nummdl).append('\'');
        sb.append(", mdltyp='").append(mdltyp).append('\'');
        sb.append(", author='").append(author).append('\'');
        sb.append(", revdat='").append(liRevdat).append('\'');
        sb.append(", sprsde='").append(sprsde).append('\'');
        sb.append(", jrnl='").append(liJRNL).append('\'');

        List<Integer> liKeyRemark = getRemarks();

        for (int key : liKeyRemark) {

            String s = getRemark(key);

            sb.append(", remark");
            sb.append(key);
            sb.append("='").append(s).append('\'');;
        }

        sb.append('}');
        return sb.toString();
    }
}
