#!/bin/env python
#
#
# This file is part Protein Engineering Analysis Tool (PEAT)
# (C) Copyright Jens Erik Nielsen, University College Dublin 2003-
# All rights reserved
#
#
# Rewritten by D Farrell, Jan 2009
#

import cgi
import sys,os, string, types
os.environ['MPLCONFIGDIR']='/tmp'
from PEATDB.Base import PDatabase
from PEATDB.Record import PEATRecord
from PEATDB.PEATTables import PEATTableModel, PEATTable
from PEATDB.web import PEATWeb
from pKD import pKDInterface

class titdbWeb(PEATWeb):

    """Class providing a web interface for PEAT projects subclassed for titration_db"""

    def __init__(self, server='localhost', project='test', port=8080,
                        user=None, passwd=None,
                        bindir='', fullpath=''):

        """bindir : path to cgi scripts in server address, usually 'titration_db'
           fullpath : file system path to script folder """

        import socket
        self.host = socket.getfqdn(socket.gethostname())

        self.sessionkey=None
        self.form=cgi.FieldStorage()
        self.server = server
        self.project = project
        self.user = user
        self.password = passwd
        self.port = port

        self.bindir = bindir
        dirname = os.path.basename(fullpath)
        self.imgdir = '/' + dirname + '/images'
        self.plotsdir = '/'+ dirname + '/plots'
        self.imagepath = fullpath + '/plots'

        action = self.form.getfirst('action')
        self.action = action
        if not self.form.getfirst('action'):
            self.show_intro()
            return
        elif action == 'show_intro':
            self.show_intro()
        elif action == 'show_help':
            self.show_help()
        elif action == 'summary':
            self.showSummary()
        elif action == 'analysis':
            self.showAnalysis()
        elif action == 'show_all':
            self.show_all()
        elif action=='show_specific':
            self.show_specific()
        elif action == 'show_datasets':
            self.show_datasets()
        elif action == 'search':
            self.show_search_results()
        elif action == 'downloads':
            self.showDownloads()
        elif action == 'selectpKD':
            self.selectpKD()
        elif action == 'showpKD':
            self.showpKD()

        return

    def show_intro(self):
        '''Show intro page'''
        self.showHeader(menu=1)

        print '<div class="main">'
        print '<right><img src="%s/intro_image.png" width=250 class="align-right"></a></right>' %self.imgdir
        print '<h1><a>Welcome to the protein NMR pH titration database.</h1></a>'

        print 'NMR-monitored pH titration experiments are routinely used to measure\
                site-specific protein pKa values. Accurate experimental pKa values are\
                essential in dissecting enzyme catalysis, in studying the pH-dependence of protein\
                stability and ligand binding and ultimately in understanding electrostatic effects in proteins.\
                It is therefore important to retain the raw NMR spectroscopic data to allow for\
                possible reinterpretation.'
        print '<br>'
        print 'This web interface provides access to a database of experimental NMR pH titration\
                curves obtained largely from published sources.'
        print '<br><br>'
        print '<a>It is designed to provide the following services:'
        print '<UL>\
                <LI>Browse contents of the DB in one table\
                <LI>Search for curves based on residue name, pka values, residue type etc.\
                <LI>Visualise and overlay any combination of curves (and our fits)\
                <LI>View some summary statistics on the current dataset\
                <LI>Download the raw data as csv/text files and refit/analyse\
                </UL>'

        print 'We are continually trying to expand the dataset and rely on researchers to voluntarily provide \
                 us with their data. If you wish to submit data to be included here, please send the \
                 tabulated data in any convenient text format (see help page) by e-mail to us at \
                 <a href="mailto:titrationdb@gmail.com">titrationdb@gmail.com</a><p>'

        print '<center><img src="%s/banner_icon.png"></center>' %self.imgdir
        print '</div>'
        self.footer()
        return

    def show_help(self):
        '''Show help page'''
        self.showHeader(menu=1)

        print '<div class="main">'
        print '<h2>The primary help information for using these pages is available <a href="http://enzyme.engr.ccny.cuny.edu/wiki/index.php/TitrationDB"> here</a></h2>'

        print '<h2>Performing searches:</h2>'
        print '<UL>\
        <LI>Searching by protein name - these can be seperated by spaces - you may match any or all the words in the search phrase, by choosing in the match drop-down list\
        <LI>Searching by pka value - enter values as ranges, eg. 3-6 or sets of ranges separated by spaces, eg. 1-2 3-4\
        <LI>Searching by residue - simply enter names of each residue, eg. GLU. Or names separated by spaces, e.g. 45 34 67 will simply return any residues with those numbers in them\
        <LI>searches are not case sensitive\
        <LI>Searching/filtering by nucleus - choose one of the three nuclei from the drop down menu, if required\
        </UL>'

        print '<h3>Please report any bugs or requests for improvements to <a href="mailto:titrationdb@gmail.com">titrationdb@gmail.com</a><br><br>'
        print 'You may cite this database using the following reference:</h3>'
        print '<b>Farrell, D., et al., Titration_DB: Storage and analysis of NMR-monitored protein pH titration curves.<br>'
        print 'Proteins: Structure, Function, and Bioinformatics, 2010 Mar;78(4):843-57</b>'
        print '<a href="http://www3.interscience.wiley.com/journal/122593997/abstract"> link </a>'
        print '</div>'

        self.footer()
        return

    def showHeader(self,title=None,menu=None):
        """Show page header"""

        imgdir = self.imgdir
        #html_header = 'Content-Type: text/html; charset=utf-8\n'
        #html_header += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/loose.dtd">\n\n'
        print "Content-type: text/html; charset=utf-8\n"
        print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'

        print '<html>'
        print '<head>'

        if title==None:
            print '<title>Protein Titration Database</title>'
        else:
            print '<title>%s</title>' % title

        print '<link href="%s/styles.css" rel="stylesheet" type="text/css" />' %self.bindir
        print '<link rel="shortcut icon" href="%s/favicon.ico" type="image/x-icon" />' %self.bindir
        print '<script type="text/javascript" src="%s/scripts/checkbox.js"></script>' %self.bindir
        print '</head>'
        print '<body>'
        print '<div class="header">'
        print '<a href="http://enzyme.engr.ccny.cuny.edu/wiki/index.php/TitrationDB">Help</a>'
        print '<a href="mailto:titrationdb@gmail.com">Contact</a>'
        print '</div>'

        print '<script type="text/javascript" src="%s/scripts/boxover.js"></script>' %self.bindir

        if menu==1:
            self.menu()
        return

    def footer(self):
        """Print page footer"""

        print '<br>'
        print '<p><center>Provided by <a href="http://enzyme.ucd.ie">Nielsen Research Group at UCD</a></center></p>'
        print '<p><center>Supported by <a href="http://www.sfi.ie">Science Foundation Ireland</a></center></p>'
        print '<center><a href="http://www.sfi.ie"><img src="%s/sfi_logo.png" width=200 border="0"></a></center></p>' %self.imgdir
        print '</div>'
        print '</div>'
        print '</body>'
        return

    def showFormButton(self, action, label, helptext=None):
        """Add a button"""

        bindir = self.bindir
        print '<form action="%s/main.cgi" METHOD="POST" ENCTYPE="multipart/form-data">'  %bindir
        self.write_sessionkey(action)
        print '<tr><td class="menu">'
        print '<input type=submit value="%s" name=submit class="btn"' %label
        if helptext != None:
            print 'title="header=[%s] body=[%s]"></td></tr>' %(label,helptext)
        else:
            print '></td></tr>'
        print '</form>'
        print
        return

    def menu(self):
        """Print the menu"""

        bindir = self.bindir
        print '<div class="menu">'
        print '<table id="menu" valign=top align=left>'
        print '<td class="menu"><b><img src="%s/titDB_logo.png" width="230"></b></td>' %self.imgdir
        print

        self.showFormButton('show_intro', 'Home')
        self.showFormButton('show_all', 'Browse Records', 'Browse all records in the DB')
        self.showFormButton('summary', 'Summary','Summary statistics')
        self.showFormButton('analysis', 'Analysis')
        self.showFormButton('downloads', 'Downloads', 'Export data in text format')
        self.showFormButton('selectpKD', 'pKD','Cross reference curves with calculated data from pKD server')
        self.showFormButton('show_help', 'Help')

        searchmessage = 'Enter your search here'
        print '<tr><td class="menu"><a>SEARCH OPTIONS</td><a></tr>'
        print '<form action="%s/main.cgi" METHOD="POST" ENCTYPE="multipart/form-data">' %bindir
        self.write_sessionkey('search')
        #protein search box
        print '<tr><td class="menu">'
        print 'Protein: '
        print '<select name="proteinmatchmethod" class="btn">'
        print '<option value="or">Any words'
        print '<option value="and">All words'
        print '</select>'
        print '</td></tr>'
        print '<td class="menu">'
        print """<input type="text" name="words" size=22 maxlength=50 value="" class="btn1"\
                title="header=[Protein search] body=[Enter multiple names seperated by a space.\
                    This search is not case sensitive]"></td></tr>"""

        #residue search box
        print '<tr><td class="menu">'
        print 'Residue: '
        print '<tr><td class="menu">'
        print """<input type="text" name="residue" size=22 maxlength=40 value="" class="btn1"\
                 title="header=[Residue type] body=[Use three letter codes,\
                 separated by a space e.g. GLU ASP LYS]"></td></tr>"""
        #pka search box
        print """<tr><td class="menu"> pKa range: <input type="text" name="pka" size=22 \
                maxlength=30 value="" class="btn1" title="header=[pKa range] body=[Enter a range of values using \
                this format: e.g. 5-6]"></td></tr>"""

        #nucleus search box
        print '<tr><td class="menu">'
        print 'Nucleus: <select name="nucleus" class="btn">'
        print '<option value="any">Any'
        print '<option value="1H">1H'
        print '<option value="15N">15N'
        print '<option value="13C">13C'
        print '</select>'
        print '</td></tr>'

        #drop list for whether to match any or all of the searches from each box
        print '<tr><td class="menu">'
        print """Match: <select name="matchmethod" class="btn" title="header=[Global match method] \
                 body=[Match records with ALL of the search criteria or only those with ANY those\
                  attributes]">"""
        print '<option value="and">All'
        print '<option value="or">Any'
        print '</select> <p>'
        print '</td></tr>'
        print '</td></tr>'
        print '<tr><td class="menu"><input type="submit" value="Search Now" class="btn"></td></tr>'
        print '</form>'

        print '</table>'
        print '</div>'
        print '<div id="content">'
        return

    def showSummary(self):
        from PEATDB.Ekin.Titration import TitrationAnalyser
        self.showHeader(menu=1)
        DB = self.DB = self.connect()
        sys.stdout.flush()
        t = TitrationAnalyser()
        ekindata = t.getEkinDicts(DB)
        print '<div class="main">'
        t.dotitDBStats(ekindata)
        print '</div>'
        return

    def showAnalysis(self):
        """Analysis of current pKas"""
        from PEATDB.Ekin.Titration import TitrationAnalyser
        self.showHeader(menu=1)
        DB = self.DB = self.connect()
        t = TitrationAnalyser()

        print '<div class="main">'
        print '<p>Selected plots below reflect some of the analysis shown in the \
               <a href="%s/paper_2010.pdf"> original paper</a> updated for the current dataset. </p>' %self.bindir
        print '<a>The distributions shown are of the change in chemical shift over all\
                detected titrations. `Reliable` pKas are those associated with\
                the largest chemical shift changes in a titration curve and that meets the criteria defined in\
                the paper. We define primary pKa values simply as the subset of the reliable pKa values \
                that originate from titration curves with with only one titration.</a>'
        sys.stdout.flush()

        colnames = ['1H NMR','15N NMR','13C NMR']

        for col in colnames:
            p = t.extractpKas(DB,col,silent=True,minspan=0.06)
            print '<div>'
            print "<h2>%s: Distribution of &Delta;&delta; for fitted pKa values</h2>" %col
            img1 = t.analysepKas(p, silent=True, prefix=col, path=self.imagepath)
            #t.makepKasTable(p)
            print '<img src="%s/%s" align=center width=800 class="plot">' %(self.plotsdir, img1)
            print '</div>'
            sys.stdout.flush()

        #compare nuclei
        img2, img3 = t.compareNuclei(DB, '15N NMR', '1H NMR', titratable=False, silent=True, path=self.imagepath)
        print '<p>Below is an analysis of the correspondence between fitted pKas for 1H and 15N \
                where they are available for the same residue in the same protein. This is the same\
                plot as figure 4 in the original paper updated for the current dataset.\
                The plots are divided into reliable and other pKas for comparison.</p>'
        print '<div>'
        print '<center><img src="%s/%s" align=center width=600 class="plot"></center>' %(self.plotsdir, img2)
        print '</div>'
        print '<p>The same plot as above broken down by residue type and shown only for titratable\
                residues.</p>'
        print '<div>'
        print '<center><img src="%s/%s" align=center width=600 class="plot"></center>' %(self.plotsdir, img3)
        print '</div>'

        self.footer()
        return

    def showDownloads(self):
        """Downloads links"""
        self.showHeader(menu=1)
        print '<div class="main">'
        print '<h2>Downloads</h2>'
        print '<h3>The entire dataset may be downloaded in text format as a single zip file.</h3>'
        print
        #print '<p>Exporting current data, please wait a moment...</p>'
        sys.stdout.flush()

        DB = self.DB = self.connect()
        from PEATDB.Ekin.Titration import TitrationAnalyser
        t = TitrationAnalyser()
        filename = t.exportAll(DB)
        print '<h2><a href="%s/%s"> download zip</a></h2>' %(self.bindir,os.path.basename(filename))
        print '</div>'
        self.footer()
        return

    def do_search(self, globalop, proteinop, proteins, residues, nucleus, pka):
        """Do searches for the various parameters, name, pka etc"""

        import re, urllib
        from PEATDB.PEATTables import PEATTableModel
        import PEATDB.Ekin.Fitting as Fitting
        from PEATDB.Ekin.Web import EkinWeb
        DB = self.DB
        EW = EkinWeb()
        ekincols = DB.ekintypes

        found=[]
        protlist = proteins.split(' ')
        residuelist = residues.split(' ')

        def createPhrase(items, op):
            if op == 'or':
                logicsymbol = '|'
            else:
                logicsymbol = '+'
            itemlist = items.split(' ')
            phrase =''
            c=1
            for i in itemlist:
                if c == len(itemlist):
                    phrase = phrase + i
                else:
                    phrase = phrase + i + logicsymbol
                c=c+1
            return re.compile(phrase, re.IGNORECASE)

        #if there is a protein name entered, get the list of proteins/records to use
        #otherwise use all records to search for the other keys
        names = []
        keywords = {}
        for p in DB.getRecs():
            name = DB[p].name
            names.append((name,p))
            if hasattr(DB[p], 'keywords'):
                keywords[name] = DB[p].keywords
            else:
                keywords[name] = ''
        names.sort()

        #create search expressions
        s_protein = createPhrase(proteins, proteinop)
        s_residue = createPhrase(residues, 'or')
        pkarange = pka.split('-')

        #do search
        for name in names:
            proteinname = name[0]
            protein = name[1]
            if s_protein.search(proteinname) or s_protein.search(keywords[proteinname]):
                found.append(protein)

        if len(found)==0:
            print '<h2>Sorry, no proteins found that match this name. <h2>'
            return
        elif residues == '' and pka == '':
            self.show_DB(selected=found)
            return found

        #now search for the other keys inside selected proteins if needed
        ekinfound={}; foundfits={}
        ignore =['__fit_matches__', '__Exp_Meta_Dat__', '__datatabs_fits__']

        kys = list(DB.getRecs())
        kys.sort()
        if len(found) == 0 or globalop == 'or':
            found = DB.getRecs()

        print '<table id="mytable" cellspacing=0 align=center>'
        '''search recs for residue and pkarange
           and add dataset names to new ekinfound dict'''
        for protein in found:
            for col in DB[protein].keys():
                if nucleus != 'any':
                    if nucleus not in col:
                        continue
                E = DB[protein][col]
                if DB['userfields'].has_key(col):
                    fieldtype=DB['userfields'][col]['field_type']
                else:
                    fieldtype=None
                if fieldtype in ekincols:
                    dk = E.datasets
                    fits = E.__datatabs_fits__
                    for k in dk:
                        meta = E.getMetaData(k)
                        try:
                            thisres = meta['residue']
                        except:
                            thisres = k
                        if thisres == None:
                            thisres = k
                        if residues != '':
                            if s_residue.search(thisres) and not k in ignore:
                                if not ekinfound.has_key(protein+'$'+col):
                                    ekinfound[protein+'$'+col]=[]
                                ekinfound[protein+'$'+col].append(k)

                        if pka != '':
                            foundpka = 0
                            if k in fits.keys():
                                if fits[k] == None:
                                    continue
                                if fits[k].has_key('model'):
                                    model = fits[k]['model']
                                else:
                                    continue
                                if not foundfits.has_key(protein+'$'+col):
                                    foundfits[protein+'$'+col]={}
                                X = Fitting.getFitter(model)
                                pnames = X.varnames
                                i=0
                                #check if first num is larger, then swap
                                try:
                                    pk1=float(pkarange[0])
                                    pk2=float(pkarange[1])
                                except:
                                    print '<h2> Error: pka values are not valid </h2>'
                                    return
                                if pk1 > pk2:
                                    tmp = pk1
                                    pk1 = pk2
                                    pk2 = tmp

                                #iterate thru parameter names and match any pK fields
                                #this code is not that efficient!
                                for p in pnames:
                                    if 'pK' in p:
                                        pka = fits[k][i]
                                        if pka >= pk1 and pka <= pk2:
                                            foundpka=1
                                    i=i+1
                                #if match is 'ANY', just append dataset if not there already
                                #also for case if no residue value entered
                                if globalop == 'or' or residues == '':
                                    if foundpka == 1:
                                        if not ekinfound.has_key(protein+'$'+col):
                                            ekinfound[protein+'$'+col]=[]
                                        if not k in ekinfound[protein+'$'+col]:
                                            ekinfound[protein+'$'+col].append(k)
                                #if match is 'ALL', need to check dataset already found
                                #and if no pka found, remove it. if both are true keep it
                                elif globalop == 'and':
                                    if foundpka == 0:
                                        if ekinfound.has_key(protein+'$'+col) and k in ekinfound[protein+'$'+col]:
                                            #print 'removing', protein, col
                                            ekinfound[protein+'$'+col].remove(k)

                                foundfits[protein+'$'+col][k]=fits[k]
        #check for empty fields in ekinfound dict and delete them..
        for d in ekinfound.keys():
            if len(ekinfound[d])==0:
                del(ekinfound[d])

        #if no results, just say that and return
        if len(ekinfound)==0:
            print '<h2> Sorry, no records found that match these parameters. </h2>'
            return
        #top display button and options
        print '<form name="resultsform" action="%s/main.cgi" METHOD="POST" ENCTYPE="multipart/form-data">' %self.bindir
        self.write_sessionkey('show_datasets')
        print '<td valign=top align=right colspan=3> <input type=submit value="display selected" name=submit>'
        print '<label><input type=checkbox name="plotoption" value=3>single graph</label>'
        print '<label><input type=checkbox name="normalise" value=1>normalise</label>'
        print '<label><input type=checkbox name="logx" value=1>log-x</label>'
        print '<label><input type=checkbox name="logy" value=1>log-y</label>'
        print '<label><input type=checkbox name="legend" value=1>legend</label>'
        print '<input type=button value="Check All" onClick="checkAll(document.resultsform.residue)">'
        print '<input type=button value="Uncheck All" onClick="uncheckAll(document.resultsform.residue)"></td>'
        print '</tr>'
        #header
        cols=['protein','column','residues']
        for c in cols:
            print '<th>'+c+'</th>'
        print '</tr>'
        ekys = ekinfound.keys()
        ekys.sort()

        r=1
        for k in ekys:
            if r % 2 == 0:
                cls = "spec"
            else:
                cls = ""

            fields = k.split('$')
            protein = fields[0]
            column = fields[1]

            proteinname = DB[protein]['name']
            print '<tr>'
            print '<th class="spec">%s</th> <td> %s </td>' %(proteinname, column)
            print '<td>'
            for residue in ekinfound[k]:
                residuefield = k+"$"+residue
                fithtml = ''
                try:
                    print '<input type=checkbox id="residue" name="residue" value=%s>' %("'"+residuefield+"'")
                except:
                    print 'UnicodeEncodeError'
                    continue

                urlfields = urllib.urlencode({'login':self.user,'project':self.project,
                                           'residue': residuefield,'action': 'show_datasets'})
                print '<a href="/cgi-bin/titration_db/main.cgi?%s" target="_blank">%s</a>'\
                          % (urlfields, residue)

            print '</tr>'
            r=r+1
        print '</form>'
        print '</table>'
        self.footer()

        return

    def show_search_results(self):
        """Display search results"""

        self.showHeader(menu=1)
        sys.stdout.flush()
        self.DB = self.connect()

        key=self.form.getfirst('key')
        matchmethod=self.form.getfirst('matchmethod')
        proteinmatchmethod=self.form.getfirst('proteinmatchmethod')
        words=self.form.getfirst('words')
        residue=self.form.getfirst('residue')
        nucleus=self.form.getfirst('nucleus')
        pka=self.form.getfirst('pka')

        print '<table bgcolor=#CD9B9B border="1" bordercolor=black cellspacing=0 cellpadding=4 valign=top \
                align=center width=80%><tr><td>'

        print '<div align=left>'
        print '<big><a>Search results for protein=%s %s residue=%s %s nucleus=%s pka=%s</big></a>'\
                  %(words,matchmethod,residue,matchmethod,nucleus,pka)
        print '<br>'
        print '</div>'
        print '</table>'
        self.do_search(matchmethod, proteinmatchmethod, words, residue, nucleus, pka)
        return

    def selectpKD(self):
        """Allow user to select a protein and field for pKD"""

        self.showHeader(menu=1)
        sys.stdout.flush()
        DB = self.DB = self.connect()

        names = []
        for p in DB.getRecs():
            names.append((DB[p].name,p))
        names.sort()
        nucl = ['1H NMR', '15N NMR', '13C NMR']

        #check pKD available calcs
        P = pKDInterface()
        pkdprots = [string.upper(e[0]) for e in P.getProteins()]

        #add selection boxes
        print '<div align=left>'
        print '<form name="pkdform" action="%s/main.cgi" METHOD="POST" ENCTYPE="multipart/form-data">' %self.bindir
        self.write_sessionkey('showpKD')

        print '<big>Select experimental curves to compare with calculated curves in pKD</big><br>'