import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;


/**
 * This class reads in feature files for shiftx2
 * feature files are output files from individual feature generator modules
 * 
 * @author yifeng
 * @date November 19th, 2009
 * @modified by B. Han
 *  
 */

public class FeatureReader {

	HashMap<Integer, Residue> residues;
	HashMap<String, String> aacodes;
	RandomCoil rcoil;
    double pH, temperature;

    // to calculate phosphorylated residue
   	public HashMap<String, PhosphoCS> hashmap_phosphoCS = new HashMap<String, PhosphoCS>();  
   	public HashMap<Integer, String> hashmap_phosphoRes = new HashMap<Integer, String>();  

	private boolean DEBUG = ShiftXp.DEBUG;
    private boolean UNLINK_FEATURE_FILES = true;
	private boolean EXIST_PHOSPHORYLATED_RESIDUE = false;
	
	/**
	 * constructor.
	 */
	public FeatureReader(RandomCoil rcoil, double pH, double temperature){
		setAACodes();		
		residues = new HashMap<Integer, Residue>();
		this.rcoil = rcoil;
        this.pH = pH;
        this.temperature = temperature;

        setPhosPhoResCS();
	}
	
    // residue type mapping
    // CYS-B will be mapped with ring_curr info from shiftx    
	public void setAACodes()	{
		String[][] aacodeArray = {{"ALA","A"}, {"CYS","C"}, {"ASP","D"}, {"GLU","E"},
				{"PHE","F"}, {"GLY","G"}, {"HIS","H"}, {"ILE","I"},
				{"LYS","K"}, {"LEU","L"}, {"MET","M"}, {"ASN","N"},
				{"PRO","P"}, {"GLN","Q"}, {"ARG","R"}, {"SER","S"},
				{"THR","T"}, {"VAL","V"}, {"TRP","W"}, {"TYR","Y"}, 
                {"SEP","pS"}, {"TPO", "pT"}, {"PTR", "pY"}, 
				{"NA","NA"}};
		
		aacodes = new HashMap<String,String>();
		for (int i=0; i < aacodeArray.length; i++ ) {
		     aacodes.put(aacodeArray[i][0], aacodeArray[i][1]);
		}		
	}

	public void setPhosPhoResCS() {
		// Residue:Atom, delta1, pKa, delta2
		String[] phosphoResAtom = {
			"SEP:P", "SEP:N", "SEP:H", "SEP:HA", "SEP:HB2", "SEP:HB3", "SEP:C", "SEP:CA", "SEP:CB",
			"TPO:P",  "TPO:N", "TPO:H", "TPO:HA", "TPO:HB", "TPO:HG2", "TPO:C", "TPO:CA", "TPO:CB", "TPO:CG2",
			"PTR:P", "PTR:N", "PTR:H", "PTR:HA", "PTR:HB2", "PTR:HB3", "PTR:HD1", "PTR:HD2", "PTR:HE1", "PTR:HE2", 
			"PTR:C",  "PTR:CA", "PTR:CB", "PTR:CD1", "PTR:CD2", "PTR:CE1", "PTR:CE2" };
		
		Double[][] phosphoCSvalues = {
			{1.16,  5.06,  5.96}, {115.5, 6.02, 118.3}, {8.65, 6.03,  9.29}, {4.60,  6.00,  4.43},
			{4.22, 6.07,  4.07}, {4.13,  0.0, 4.07},  {174.9, 5.99, 175.9}, {57.40, 0.0,  58.46},
			{66.81, 6.04, 65.65},
			{0.22, 6.30, 4.51}, {113.4, 6.35, 119.5}, {8.43, 6.34, 9.21}, {4.43, 6.33, 4.18},
			{4.66, 6.33, 4.41}, {1.33, 6.34, 1.27}, {175.1, 6.33, 175.9}, {61.77, 6.35, 63.57},
			{74.02, 6.31, 71.66}, {20.69, 6.30, 20.98},
			{-3.07, 5.96, 0.95}, {120.1, 0.0, 120.3}, {8.21, 0.0, 8.23}, {4.61, 0.0, 4.59},
			{3.15, 0.0, 3.12}, {3.02, 0.0, 3.01}, {7.22, 6.03, 7.17}, {7.22, 6.03, 7.17},
			{7.14, 0.0, 7.13}, {7.14, 0.0, 7.13}, {176.7, 0.0, 176.7}, {57.04, 0.0, 57.04},
			{38.73, 0.0, 38.70}, {133.02, 5.99, 132.61}, {133.02, 5.99, 132.61}, {123.19, 0.0, 123.19},
			{123.19, 0.0, 123.19} };
		
		for (int i=0; i < phosphoResAtom.length; i++ ) {
		     hashmap_phosphoCS.put(phosphoResAtom[i], new PhosphoCS(phosphoCSvalues[i][0], phosphoCSvalues[i][1], phosphoCSvalues[i][2]));
		}		
	}
	

    public double getPhosphoCS(String restype, String atomname) {
	    PhosphoCS pcs;
		String key;
        double diff_ph_pka, deltaCS = 0.0;

	    key = restype+':'+atomname;
	    // System.out.println("Key -> " + key);

        pcs = hashmap_phosphoCS.get(key);

        // System.out.println(key +" " + pcs.delta1 + " " + pcs.pKa + " " + pcs.delta2);

        if (pcs.pKa == 0.0 ) { diff_ph_pka = 0.0; }
        else { diff_ph_pka = this.pH - pcs.pKa; }
        
        deltaCS = ( pcs.delta2 * Math.pow(10, diff_ph_pka) + pcs.delta1 ) / ( 1 + Math.pow(10, diff_ph_pka) );

		return Math.floor(deltaCS * 1000) / 1000;
	}


	public HashMap<Integer, Residue> getResidues(){ 
		return this.residues; 
	}

    /**
     * set/update additional features with read features  
     * set pH, temperature
     */
    public void set_additional_features() {
        Residue res, res_temp;
        ArrayList<Integer> keys_resno = new ArrayList<Integer>(this.residues.keySet());
        Collections.sort(keys_resno); 

        for (Integer resno : keys_resno) {
            res = this.residues.get(resno);   

            res.ph = this.pH;
            res.temperature = this.temperature; 

            res_temp = this.residues.get(resno-2);
            if (res_temp != null) res.iupac_p2 = res_temp.iupac;        
            res_temp = this.residues.get(resno+2);
            if (res_temp != null) res.iupac_n2 = res_temp.iupac;        

            res_temp = this.residues.get(resno-1);
            if (res_temp != null) {
                res.iupac_p = res_temp.iupac;        
                res.ss_p = res_temp.ss;        
                res.hbond_p = res_temp.hbond;        
                res.dbond_p = res_temp.dbond;        
                res.ha1dist_p = res_temp.ha1dist;
                res.ha2dist_p = res_temp.ha2dist;
                res.hndist_p  = res_temp.hndist;
                res.ordist_p  = res_temp.ordist;
            }

            res_temp = this.residues.get(resno+1);
            if (res_temp != null) {
                res.iupac_n = res_temp.iupac;        
                res.ss_n = res_temp.ss;        
                res.hbond_n = res_temp.hbond;        
                res.dbond_n = res_temp.dbond;        
                res.ha1dist_n = res_temp.ha1dist;
                res.ha2dist_n = res_temp.ha2dist;
                res.hndist_n  = res_temp.hndist;
                res.ordist_n  = res_temp.ordist;
            } 
        }
    }


	/**
	 * read in all files.
	 * @param filePrefix
	 */
	public void readAllFiles(String filePrefix){
	    if(DEBUG) System.out.println("\nReading Features ......\n");
         
        this.readPhosphoRes(filePrefix + ".phos");
        
      	this.readResMF(filePrefix + ".resmf");   // VADAR + HyDRO
		this.readEffects(filePrefix + ".effects");  //RingCurr
		this.readAngles(filePrefix + ".angles");
		this.readAreaAtom(filePrefix + ".resmf.asa");

        set_additional_features();

	    if(DEBUG) System.out.println("Features are loaded\n\n");
	}
	
	// set atom names & random coil value for each residue type	
	public void setAtoms(Residue res)  { 
        String iupac  = this.aacodes.get(res.resName);
		ArrayList<String> atomnames = new ArrayList<String>(rcoil.getAtoms(iupac));

    	for ( String s: atomnames ) {
			Atom atom = new Atom();
			atom.atomName = s;
			atom.rcoil = rcoil.getRCoil(iupac, s);
			res.atoms.put(s, atom);
			// System.out.println("res: "+ res.resNum + "-" + iupac+ "  atom: " + s + " rcoil:"+atom.rcoil);
		}
	}
	
    
	// For phosphorylated residue, set atom names & random coil value for each residue type	
	public void setAtomsPhosphoRes(Residue res)  { 
        String restype  = res.resName;
        String pIupac = null;

        if (restype.equals("SER")) { restype = "SEP"; pIupac = "pS"; }
        else if (restype.equals("THR")) { restype = "TPO"; pIupac = "pT"; }
        else if (restype.equals("TYR")) { restype = "PTR"; pIupac = "pY"; }

		ArrayList<String> atomnames = new ArrayList<String>(rcoil.getAtoms(pIupac));

    	for ( String s: atomnames ) {
			Atom atom = new Atom();
			atom.atomName = s;
			atom.rcoil = getPhosphoCS(restype, s);
			res.atoms.put(s, atom);
			// System.out.println("res: "+ res.resNum + "-" + pIupac+ "  atom: " + s + " rcoil:"+atom.rcoil);
		}
	}
	

/**
	 * read in phosphorylated residue
	 * @param filename
	 * @return
	 */
	public void readPhosphoRes(String filename){
		
		File wf = new File(filename);
		
		if(! wf.exists()){   
			// System.err.println("# Note: Phosphorylated Residues are not in the ["+filename+"]");
			EXIST_PHOSPHORYLATED_RESIDUE = false;				
            // System.exit(1);
		} else {
			EXIST_PHOSPHORYLATED_RESIDUE = true;
		    // System.out.println("# Read Phosphorylated Residue");

			BufferedReader br;
			String line;
			String[] fields;
			try{
				br = new BufferedReader(new InputStreamReader(new FileInputStream(wf)));

				while(br.ready()) {
					line = br.readLine();
					fields = line.split(",");
	                                
	                // generate new phosphorylated residue hashmap
	                // System.out.println(fields[0].trim() +" : "+ fields[1].trim());
					hashmap_phosphoRes.put(Integer.parseInt(fields[0].trim()), fields[1].trim());
				}
				br.close();
				
				if(UNLINK_FEATURE_FILES) wf.delete();

			} catch(IOException ex){
				ex.printStackTrace();
			}
		}
	}
	
	/**
	 * read in major features from resmf's output
	 * @param filename
	 * @return
	 */
	public void readResMF(String filename){
		
		File wf = new File(filename);
		
		if(! wf.exists()){   
			System.err.println("\n************************************************************");
			System.err.println("Error: file ["+filename+"] does not exist");
			System.err.println("************************************************************\n\n");

            System.exit(1);
		}
		
		String pResName;

		BufferedReader br;
		String line;
		String[] fields;

		try{
			br = new BufferedReader(new InputStreamReader(new FileInputStream(wf)));

			// discard two head lines
			if(br.ready()) {
				br.readLine();  
				br.readLine();  
			}

			while(br.ready()) {
				line = br.readLine();
				fields = line.split("\\s+");
                                
                // generate new residue object 
				Residue res = new Residue();

                // assign with reading features
				res.resNum = Integer.parseInt(fields[0].trim());
				res.resName = fields[1].trim();

                if ( fields[2].trim().equals("-nan") ) { 
			            System.err.println("\n************************************************************");
                        System.out.println("Error: PDB file may has non-standard format residue type"
                                            + "\n\tin the first chain.\n");
			            System.err.println("************************************************************\n\n");
                        System.exit(-1);
                } else if ( fields[2].trim().equals("nan") ) { 
                    // add flag to note 'problem' in the output
                    if(DEBUG) System.out.println("resNum:" + res.resNum + " has nan for ASA \n" + line );

					res.resASA  = 0.0;
					res.fracASA = 0.0;
					res.resVol  = 0.0;
					res.fracVol = 0.0;
					  
					res.sideASA = 0.0;
					res.sideFracASA  = 0.0;
					res.SFE     = 0.0;
                } else {
					res.resASA  = Double.parseDouble(fields[2]);
					res.fracASA = Double.parseDouble(fields[3]);
					//res.resVol  = Double.parseDouble(fields[4]);
					//res.fracVol = Double.parseDouble(fields[5]);
					res.resVol  = 0.0;
					res.fracVol = 0.0;
					  
					res.sideASA = Double.parseDouble(fields[6]);
					res.sideFracASA  = Double.parseDouble(fields[7]);
					res.SFE     = Double.parseDouble(fields[8]);
                } 
				res.hydro   = Double.parseDouble(fields[9].trim());

				this.residues.put(res.resNum, res);

                // generate atom structure with residue information (iupac)
                pResName = hashmap_phosphoRes.get(res.resNum);
				if(pResName != null) setAtomsPhosphoRes(res);
				else setAtoms(res);	
				
				// set atom names for each residue with Random Coil Table
			}
			br.close();
			
			if(UNLINK_FEATURE_FILES) wf.delete();

			
		} catch(IOException ex){
			ex.printStackTrace();
		}		

	}
	
	/**
	 * read in the ring current file.
	 * @param filename
	 */
	public void readEffects(String filename){
		File wf = new File(filename);
		
		if(! wf.exists()){
			System.err.println("\n************************************************************");
			System.err.println("Error: file ["+filename+"] does not exist");
			System.err.println("************************************************************\n\n");
            System.exit(1);
		}
		
		BufferedReader br;
		String line;
		String[] fields;
		Atom atom;
                String[] cysAtoms = {"CA","CB","HB2","HB3"};  // for change different Random Coil value when ssbond (cys) 

		try{
			br = new BufferedReader(new InputStreamReader(new FileInputStream(wf)));

			if(br.ready()){
				br.readLine(); // discards header.
			}

			while(br.ready()){
				line = br.readLine();
				fields = line.split(",");
				// String pdbid = fields[0].trim();
				Integer resNum = new Integer(fields[0].trim());
				Residue res = this.residues.get(resNum);

				if(res == null){
                      this.residues.put(resNum, new Residue());
                      res = this.residues.get(resNum);
				}
				assert(res.resName.equals(fields[1].trim()));

                // if(res.resNum == 1) System.out.println(line);
				res.iupac = fields[2].trim();
				res.dbond = fields[2].trim().equals("B") ? 1:0 ; 
				res.ss = fields[3].trim();
				res.hbond = fields[4].trim().equals("T") ? 1:0 ;
				//res.bturn = fields[6].trim();

				//res.id = pdbid;
                // this is only for making instance
				res.id = filename.substring(filename.lastIndexOf('/')+1,filename.lastIndexOf('.'));

				String atomName = fields[5];

                // add 2011.4.26 because of nan in ringcurrent & shiftx_pred
                double ringcurrent, sxpred;
                if ( fields[6].trim().equals("nan") )  { ringcurrent = 0.0; } 
                else { ringcurrent = Double.parseDouble(fields[6].trim()); } 

				double electrostat = Double.parseDouble(fields[7].trim());
				double hbondeffect = Double.parseDouble(fields[8].trim());

                if ( fields[9].trim().equals("nan") )  { sxpred = -999.0; } 
                else { sxpred = Double.parseDouble(fields[9].trim()); };

				res.ha1dist = Double.parseDouble(fields[10].trim());
				res.ha2dist = Double.parseDouble(fields[12].trim());
				res.hndist  = Double.parseDouble(fields[12].trim());
				res.ordist  = Double.parseDouble(fields[13].trim());


				atom = res.atoms.get(atomName);
				if(atom != null){
					atom.rcurr  = ringcurrent;
					atom.electrostat  = electrostat;
					atom.hbondeffect  = hbondeffect;
					atom.sxpred = sxpred;

                    // if ssbond exists, update random coil value for some atoms
                    if ( res.iupac.equals("B") 
                         && ( atomName.equals("CA") || atomName.equals("CB")
                              || atomName.equals("HB2") || atomName.equals("HB3") ) ) { 

                         atom.rcoil = rcoil.getRCoil(res.iupac, atomName );
                    }	
                    else assert(res.iupac.equals(fields[2].trim()));				
				}					
			}
			br.close();
			
			if(UNLINK_FEATURE_FILES) wf.delete();
			
		}catch(IOException ex){
			ex.printStackTrace();
		}		
	}

	
	/**
	 * read in areaatom file.
	 * @param filename
	 */
	public void readAreaAtom(String filename){
		File wf = new File(filename);
		
		if(! wf.exists()){
			System.err.println("\n************************************************************");
			System.err.println("Error: file ["+filename+"] does not exist");
			System.err.println("************************************************************\n\n");
                        System.exit(1);
		}
		
		//HashMap<String, Atom> atoms = new HashMap<String, Atom>(); // mapping from atom name to Atom object
		BufferedReader br;
		String line;
		String[] fields;
		try{
			
			br = new BufferedReader(new InputStreamReader(new FileInputStream(wf)));

			// discard the first two header lines.
/*
			while(br.ready()){
			        line = br.readLine();
                                if (!line.trim().equals("") && line.contains("BEGIN") ) {
                                      break;
                                }
			}
*/
			while(br.ready()){
				line = br.readLine();

                // ## System.out.println(line);

				if(line.trim().equals("")){
					break;  // reach the end of the record 
				}
				fields = line.split("\\s+"); // space separated.

				Integer resNum = new Integer(fields[0].trim());
				Residue res = this.residues.get(resNum);
				if(res == null){
					this.residues.put(resNum, new Residue());
					res = this.residues.get(resNum);
					res.resNum = resNum;
					res.resName = fields[1].trim();
				}
				String atomName = fields[2].trim();

		 	    double asa = 0.0;

                if (! fields[3].trim().equals("nan") ) {
				     Atom atom = res.atoms.get(atomName);
				     if(atom != null) {  	
                           atom.asa = Double.parseDouble(fields[3].trim());
				          // System.out.println("[AREA] resnum: "+resNum+"\tatomname:"+atomName+"\tasa:"+atom.asa);
                     }
                } else {
                    // add flag to note 'problem' in the output
                    System.out.println("Problem (Reading ASA)\n resNum:" + resNum + " atomName:" + atomName + " is nan ");
                }

			} // end of while: read a line from the asa file
			br.close();
			
			if(UNLINK_FEATURE_FILES) wf.delete();
			
		} catch(IOException ex){
			ex.printStackTrace();
		}		
	}

	/**
	 * read in the angle file.
	 * @param filename
	 */
	public void readAngles(String filename){
		File wf = new File(filename);

		if(! wf.exists()) {
			System.err.println("file ["+filename+"] does not exist");
			return;
		}
		
		BufferedReader br;
		String line;
		String[] fields;
		try{
			br = new BufferedReader(new InputStreamReader(new FileInputStream(wf)));

			//discards the first header line
			if(br.ready()){
				br.readLine(); //header.
 			}
			
			while(br.ready()){
				line = br.readLine();
				fields = line.split(",");
				Integer resNum = new Integer(fields[0].trim());
				Residue res = this.residues.get(resNum);
				if(res == null){
					this.residues.put(resNum, new Residue());
					res = this.residues.get(resNum);
				}
				assert(res.resName.equals(fields[1].trim()));

				// 0 NUM,
				// 1 ResName,
				// 2 phi
				res.phi = Double.parseDouble(fields[2].trim());
				// 3 psi 
				res.psi = Double.parseDouble(fields[3].trim());
				// 4 omega
				res.omega = Double.parseDouble(fields[4].trim());
				// 5 chi
				res.chi = Double.parseDouble(fields[5].trim());
				// 6 chi2
				res.chi2 = Double.parseDouble(fields[6].trim());
				// 7 chi3
				res.chi3 = Double.parseDouble(fields[7].trim());
				// 8 theta_donor
				res.theta_donor = Double.parseDouble(fields[8].trim());
				// 9 kappa_donor
				res.kappa_donor = Double.parseDouble(fields[9].trim());
				// 10 theta_recp
				res.theta_acceptor = Double.parseDouble(fields[10].trim());
				// 11 kappa_recp
				res.kappa_acceptor = Double.parseDouble(fields[11].trim());
				// 12 phi_p
				res.phi_p = Double.parseDouble(fields[12].trim());
				// 13 psi_p
				res.psi_p = Double.parseDouble(fields[13].trim());
				// 14 omega_p
				res.omega_p = Double.parseDouble(fields[14].trim());
				// 15 chi_p
				res.chi_p = Double.parseDouble(fields[15].trim());
				// 16 phi_n
				res.phi_n = Double.parseDouble(fields[16].trim());
				// 17 psi_n
				res.psi_n = Double.parseDouble(fields[17].trim());
				// 18 omega_n
				res.omega_n = Double.parseDouble(fields[18].trim());
				// 19 chi_n
				res.chi_n = Double.parseDouble(fields[19].trim());
				// 20 phi_p2
				res.phi_p2 = Double.parseDouble(fields[20].trim());
				// 21 psi_p2
				res.psi_p2 = Double.parseDouble(fields[21].trim());
				// 22 omega_p2
				res.omega_p2 = Double.parseDouble(fields[22].trim());
				// 23 chi_p2
				res.chi_p2 = Double.parseDouble(fields[23].trim());
				// 24 phi_n2
				res.phi_n2 = Double.parseDouble(fields[24].trim());
				// 25 psi_n2
				res.psi_n2 = Double.parseDouble(fields[25].trim());
				// 26 omega_n2
				res.omega_n2 = Double.parseDouble(fields[26].trim());
				// 27 chi_n2
				res.chi_n2 = Double.parseDouble(fields[27].trim());

				// 28 theta
				res.theta = Double.parseDouble(fields[28].trim());
				// 29 kappa
				res.kappa = Double.parseDouble(fields[29].trim());

			}
			br.close();
			
			if(UNLINK_FEATURE_FILES) wf.delete();
			
		} catch(IOException ex) {
			ex.printStackTrace();
		}		
	}
	

        /**
	* encoding the bturn values for the string comparison in models
	*  bturn(from vadar): I,II,III,I',II',III',VIa,<NULL>
	*  --> A,B,C,D,E,F,G,NA
	* @param bturn
        */
    public String encoding_bturn(String bturn)
	{
              String s = null;
              
	      if (bturn.equals("I"))          s = "A";
	      else if (bturn.equals("II"))    s = "B";
	      else if (bturn.equals("III"))   s = "C";
	      else if (bturn.equals("I\'"))   s = "D";
	      else if (bturn.equals("II\'"))  s = "E";
	      else if (bturn.equals("III\'")) s = "F";
	      else if (bturn.equals("VIa"))   s = "G";
	      else  s = "NA";  // NA -> N, because 'A'

//	      System.out.println("bturn: " + bturn +" -> " + s); 

	      return s;
	}


}

