# Some basic file io.
#
# Author       : Chris X. Weichenberger
# Created      : 2011-07-13
# License
# Twilight is a GUI for displaying ligands incompatible with electron density.
#    Copyright (C) 2012  Christian X. Weichenberger, Edwin Pozharski, and
#    Bernhard Rupp.
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Modifications:
#  2012-05-14 (CW) ReadTextFile treats non-newline terminated lines correctly.
#  2012-08-01 (CW) Data directory now dependent on the location of this file.
#  2012-08-02 (CW) Fixed table separator in CTable, can be supplied now.
#  2012-10-18 (CW) Some more constants.

import os
import sys
import csv
import string
import platform

# Directory where installation resides.
INSTALLDIR = os.path.abspath(os.path.dirname(__file__));
# main directory with all data files.
DATADIR    = os.path.join(INSTALLDIR, "data");
# Directory where EDS data files have been downloaded to. Incremental update.
EDSDIR     = os.path.join(DATADIR, "EDSDB");
# A version with release date and software string added.
PDBLISTFN  = os.path.join(DATADIR, "PDBentries-4ligands-2012-01-15-d.csv");
# Information about refinement program which is not available in PDB adv query.
REFINEPROGS= os.path.join(DATADIR, "refineprogs-2012-01-15.tsv");
# Periodic table of the elements.
ELEMENTSFN = os.path.join(DATADIR, "list-of-elements.tsv");
# Empty table template file name.
EMPTYFN    = os.path.join(DATADIR, "template-22-cols.tsv");
# File name for new file from scratch.
UNTITLEDFN = "Untitled.tsv";
# Redirected output file name of combine.py, also input file for postfilter.py
COMBINEDFN = "combine-2012-01-15-d.tsv";
# Patch for conversion Release Date -> Deposit Date
DEPOSITFN  = os.path.join(DATADIR,"rel-dep-date-2012-10-15-for-2012-01-15.tsv");
LPETIDESFN = os.path.join(DATADIR, "L-peptides.csv");
BUFFERNMFN = os.path.join(DATADIR, "buffermolecules.csv");
THREE2ONE  = { "GLY": "G", "ALA": "A", "VAL": "V", "SER": "S", "THR": "T",
               "CYS": "C", "PRO": "P", "LEU": "L",    "ILE": "I", "ASP": "D",
               "ASN": "N", "HIS": "H", "PHE": "F", "TYR": "Y", "TRP": "W",
               "MET": "M",    "GLU": "E", "GLN": "Q", "LYS": "K", "ARG": "R" }
STDAMINOS  = THREE2ONE.keys();
NONLIGANDS = STDAMINOS + ["MSE", "HOH", "DOD"];

STDBASES   = [ "  A", "  C", "  G", "  T", "  U"];
DBASES     = [ " DA", " DC", " DG", " DT", " DU"];

# This should be used wherever an expected float value is not available.
NOVALIDFLOATSTR = "-66.6";
# Invalid date, eg. if PDB entry has become obsolote or parsing error.
INVALIDPDBDATE  = "01-JAN-60";
# The same date, but in the format as it is stored in the table.
INVALIDTABDATE  = "2060-01-01";
# Indices of column in data files. As data is appended in several steps, this
# should remain a constant, as long as they are present in the respective
# file. 
PDBIDI    = 0;
RESNMI    = 1;
RESNRI    = 2;
RSCCI     = 3;
NRATMSI   = 7;
DATEI     = 10;
RESOLI    = 11;
CLASSI    = 18;
VALIDCOLI = 19;

# Types of valid molecules
# Valid by filter criteria and bad RSCC.
VALID_BAD     = "Y";
# Valid by filter criteria and good RSCC.
VALID_GOOD    = "G";
# Manually added.
VALID_ADDED   = "A";
# None of the above.
VALID_NEITHER = "N"

# RSCC threshold used in the postfilter.py program.
RSCCBADTH     = 0.6;
RSCCGOODTH    = 0.95;       


def ReadTextFile(ffn):
    """Read a text file and return its content. Cut newlines at the end
    * return: List of Strings or None if file could not be found.
    """
    try:
        fh    = open(ffn);
        lines = [x[:-1] if x[-1]=="\n" else x for x in fh.readlines()];
        fh.close();
        return lines;
    except IOError:
        sys.stderr.write("Could not open file ``%s''\n" % ffn);
        return None

def CalcCombinedScore(rscc, resol):
    """Compute the combined score from RSCC and experimental resolution.
    @param rscc  RSCC value.
    @param resol  Resolution of X-ray structure.
    """
    return 2.0/(abs(float(rscc))/RSCCBADTH + float(resol)/1.3)

class CTable(list):
    def __init__(s, ffn, ints={"key": "%d"}, floats={"key": "%3.2f"}, sep="\t"):
        """Reads in a table and converts rows to their respective data types.
        Keys are the column headers!
        s.FFN: Full file name.
        @param sep  Separator for columns.
        """
        s._Ints  = ints;
        s._Floats= floats;

        list.__init__(s);
        s._ReadTable(ffn, sep);
        
    def _ReadTable(s, ffn, sep):
        """Read a tabular file"""
        s.FFN = ffn;
        fh = open(ffn);
        reader = csv.reader(fh, delimiter=sep)
        s._Headers = reader.next();
        for line in reader:
            di = {};
            for k, v in zip(s._Headers, line):
                if( k in s._Ints):   v = int(v);
                if( k in s._Floats):
                    if( v!="" ):
                        v = float(v);
                    else:
                        v = float(NOVALIDFLOATSTR);
                di[k] = v;
            s.append(di);
    
    def GetFileName(s):
        return s.FFN;

    def AsTable(s, remove=(), add=()):
        order = [x for x in s._Headers if x not in remove] + \
                [x for x in add];
        tab = [ [elem[k] for k in order] for elem in s ];
        return tab;
            
    def Write(s, fh=sys.stdout, remove=(), add=(),
              header=True, sortCol="", sep="\t"):
        """Write the table.
        @param fh  Output file handle.
        @param remove  List of column names to remove in the table.
        @param add  List of column names to add to the output.
        @param header  True to output header line.
        @param sortCol  Name of column to sort before outputting.
        @param sep  Column separator in output file.
        """
        order = [x for x in s._Headers if x not in remove] + \
                [x for x in add];

        if( sortCol!="" ):
            s.sort( lambda a,b: -(a[sortCol]<b[sortCol]) );        
        output= [];
        if( header ):
            fh.write("\t".join(order) + os.linesep );
        for elem in s:
            lst = [];
            for key in order:
                if(   key in s._Ints ):
                    st = s._Ints[key] % elem[key] ;
                elif( key in s._Floats ):
                    st = s._Floats[key] % elem[key];
                else:
                    # string
                    st = elem[key];
                lst.append(st);
            output.append( sep.join(lst) );
        fh.write( "\n".join(output) );
        if( len(output)>=1 ):
            # string.join does make N-1 concatenations for N entries in a list. 
            fh.write("\n");
