#!/usr/bin/env python
# -*- coding: utf-8 -*-
# dnacurve_cgi.py

# Copyright (c) 2005-2015, Christoph Gohlke
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * 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.
# * Neither the name of the copyright holders nor the names of any
#   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.

"""DNA Curvature Analysis - A common gateway interface for dnacurve.py.

Run ``python dnacurve_cgi.py`` to execute the script in a local web server.

:Author: `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`_

:Version: 2015.01.29

Requirements
------------
* `Python 2.7 or 3.4 <http://www.python.org>`_
* `Dnacurve.py 2015.01.29 <http://www.lfd.uci.edu/~gohlke/>`_

"""

from __future__ import division, print_function

import os
import sys
import cgi
from hashlib import md5

if sys.version_info[0] == 2:
    from urlparse import urlunsplit
    _bytes = lambda x, y: str(x)
else:
    from urllib.parse import urlunsplit
    _bytes = bytes

import dnacurve

__version__ = '2015.01.29'
__docformat__ = 'restructuredtext en'


def response(fields, page=None, form=None, result=None, maxlen=dnacurve.MAXLEN,
             fpath='', svg=False, log=None):
    """Return HTML document from submitted form data."""

    if page is None:
        page = """<!DOCTYPE html PUBLIC
        "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
        "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml"
              xmlns:mathml="http://www.w3.org/1998/Math/MathML"
              xmlns:svg="http://www.w3.org/2000/svg" xml:lang="en">
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="generator" content="dnacurve_cgi.py" />
        <meta name="robots" content="noarchive, nofollow" />
        <title>DNA Curvature Analysis</title>
        </head>
        <body>
        <h1><a href="%(url)s" style="text-decoration:none;color:#000000">
        DNA Curvature Analysis</a></h1>
        %(form)s
        <div class="content">
        %(result)s
        </div>
        </body>
        </html>
        """

    if form is None:
        form = """
        <form id="form" method="post" action="">
        <div><label><strong>Sequence:</strong> <span
        style="color:#555555">(max %(maxlen)i nucleotides)</span><br />
        <textarea name="seq" id="seq" rows="6" cols="40" style="width:99%%;
        margin:0.25em 0 1em 0">%(sequence)s</textarea></label></div>
        <div style="display:inline">
        <label><strong>Model:</strong>
        <select name="mod" id="mod">
        %(models)s
        </select>
        </label>
        &#160;<a href="?q=models" rel="nofollow" title="Show models">?</a>
        </div>
        <div style="float:right">
        <input type="reset" value="Reset" onclick="window.location='%(url)s'"/>
        <input type="submit" value="Submit" style="width:8em" />
        </div>
        </form>
        """

    if result is None:
        result = """
        <p>This form calculates the global 3D structure of a DNA molecule
        from its nucleotide sequence according to the dinucleotide wedge model.
        Local bending angles and macroscopic curvature are analyzed.</p>
        <p>Try the
        <a href="" onclick="javascript:document.forms.form.seq.value='%(s1)s';
        return false;">Kinetoplast</a> or
        <a href="" onclick="javascript:document.forms.form.seq.value='%(s2)s';
        return false;">Phased AAAAAA</a> sequences for example.</p>
        <p>For each nucleotide at position i of the input sequence,
        the following values are calculated:</p>
        <ul>
        <li>the <strong>3D coordinates</strong> of the helix axis and
        5' Phosphate atoms of a B-DNA.</li>
        <li>the <strong>curvature</strong>, which is the inverse of
        the radius of a circle passing through helix axis coordinates at
        i-10, i, and i+10, relative to the curvature in a nucleosome.</li>
        <li>the <strong>curvature angle</strong> between the smoothed
        basepair normal vectors at i-15 and i+15, relative to the curvature
        in a nucleosome.</li>
        <li>the local <strong>bend angle</strong> between the basepair
        normal vectors at i-2 and i+2.</li>
        </ul>
        <p><strong>Output files</strong> of the DNA curvature analysis are:</p>
        <ul>
        <li>a plot of the helix backbone coordinates and curvature values.</li>
        <li>all calculated values in CSV format.</li>
        <li>the helix backbone coordinates in PDB format.</li>
        </ul>
        <h3>References</h3>
        <ul>
        <li><a href="http://www.ncbi.nlm.nih.gov/pubmed/2006170">Curved DNA
        without A-A: experimental estimation of all 16 DNA wedge angles</a>.
        <br />Bolshoy A. et al; Proc Natl Acad Sci USA 88; 2312-6 (1991)</li>
        <li><a href="http://www.ncbi.nlm.nih.gov/pubmed/7816643">Bending and
        curvature calculations in B-DNA</a>. <br />Goodsell DS; Dickerson RE;
        Nucleic Acids Res 22; 5497-503 (1994)</li>
        <li><a href="http://www.ncbi.nlm.nih.gov/pubmed/3271483">A comparison
        of six DNA bending models</a>. <br />Tan RK; Harvey SC;
        J Biomol Struct Dyn 5; 497-512 (1987)</li>
        <li><a href="http://www.ncbi.nlm.nih.gov/pubmed/3456570">Curved DNA:
        design, synthesis, and circularization</a>. <br />Ulanovsky L. et al;
        Proc Natl Acad Sci USA 83; 862-6 (1986)</li>
        </ul>
        <h3>Disclaimer</h3>
        <p>Because this service is provided free of charge, there is no
        warranty for the service, to the extent permitted by applicable law.
        The service is provided &quot;as is&quot; without warranty of any kind,
        either expressed or implied, including, but not limited to, the implied
        warranties of merchantability and fitness for a particular purpose.
        The entire risk as to the quality and performance is with you.</p>
        <h3>Credits</h3>
        <p>Developed by
        <a href="%(url)s">Christoph Gohlke</a>,
        <a href="http://www.lfd.uci.edu">Laboratory for Fluorescence
        Dynamics</a>.
        Source code is available under BSD license:
        <a href="%(url)scode/dnacurve.py.html">dnacurve.py</a>
        and
        <a href="%(url)scode/dnacurve_cgi.py.html">dnacurve_cgi.py</a></p>
        """ % {"s1": "".join(dnacurve.Sequence.KINETOPLAST.split())[:maxlen],
               "s2": (dnacurve.Sequence.PHASED_AAAAAA * 14)[:maxlen],
               "url": "http://www.lfd.uci.edu/~gohlke/"}

    if fields.getfirst('q') == 'models':
        result = models()
        form = ""
    else:
        seq = fields.getfirst('seq')
        mod = fields.getfirst('mod')

        if not seq:
            seq = ""
        if seq:
            result = analyze(seq, mod, maxlen=maxlen, fpath=fpath,
                             svg=svg, log=log)

        select_options = []
        for model in dnacurve.MODELS:
            if model == mod:
                temp = "<option value='%s' selected='selected'>%s</option>"
            else:
                temp = "<option value='%s'>%s</option>"
            label = getattr(dnacurve.Model, model)['name']
            select_options.append(temp % (cgi.escape(model, True),
                                          cgi.escape(label, True)))

        form = form % {
            'sequence': cgi.escape(seq, True),
            'models': "\n".join(select_options),
            'url': script_url(),
            'maxlen': maxlen}

    return page % {'form': form, 'result': result, 'url': script_url()}


def models():
    """Return list of dnacurve models as HTML string."""

    result = ["<h2>Curvature Models</h2>"]
    for model in dnacurve.MODELS:
        lines = str(dnacurve.Model(model)).splitlines()
        result.extend((
            "<h3>%s</h3>" % cgi.escape(lines[0], True),
            "<pre>%s</pre>" % cgi.escape("\n".join(lines[1:]), True)))
    result.append(
        "<p><a href='%s'>Return to the input form</a>.</p>" % script_url())
    return "\n".join(result)


def analyze(sequence, model, maxlen, fpath, svg=False, log=None):
    """Return results of DNA curvature analysis as HTML string."""
    if svg:
        template = """
        <h2>Results</h2>
        <ul>
        <li><a href='%(fname)s.csv'>Calculated values</a> (CSV format)</li>
        <li><a href='%(fname)s.pdb'>Helix coordinates</a> (PDB format)</li>
        <li><a href='%(fname)s.svg'>Plot of coordinates and curvatures</a>
          (SVG format)</li>
        </ul>
        <object data='%(fname)s.svg' type='image/svg+xml'
          style='width:600px;height:750px;padding:0'>
        Your browser can not display Scalable Vector Graphics (SVG).
        </object>
        """
    else:
        template = """
        <h2>Results</h2>
        <img src='%(fname)s.png' alt='Plot' style='border:solid 1px'>
        <ul>
        <li><a href='%(fname)s.csv'>Calculated values</a> (CSV format)</li>
        <li><a href='%(fname)s.pdb'>Helix coordinates</a> (PDB format)</li>
        </ul>
        """

    html = []
    try:
        seq = dnacurve.Sequence(sequence)
        if len(seq) > maxlen:
            raise ValueError("sequence is too long")
        if len(seq) < 10:
            raise ValueError("sequence is too short")
        mod = dnacurve.Model(model)
        name = md5(_bytes(seq.string, 'ascii')).hexdigest()
        seq.name = name[:13]
        fname = fpath + name + str(dnacurve.MODELS.index(model))
        fpath = os.path.join(script_path(), fname)
        if not os.path.exists(fpath + '.csv'):
            results = dnacurve.CurvedDNA(seq, mod)
            results.save_csv(fpath + '.csv')
            results.save_pdb(fpath + '.pdb')
            results.plot(fpath + ('.svg' if svg else '.png'))
            if log:
                log(fname)
    except IOError as e:  # Exception as e:
        e = str(e).splitlines()
        text = e[0][0].upper() + e[0][1:]
        details = '\n'.join(e[1:])
        html.append(
            "<h2>Error</h2><p>%s</p><pre>%s</pre>" % (text, details))
    else:
        html.append(template % {'fname': fname})
    return "\n".join(html)


def script_path():
    """Return path to CGI script."""
    result = os.getenv('PATH_TRANSLATED')
    if '.py' in result:
        result = os.path.dirname(result)
    return result


def script_url(env=os.getenv):
    """Return URL of CGI script, without script name if that is index.py."""
    netloc = env('SERVER_NAME')
    port = env('SERVER_PORT')
    path = env('SCRIPT_NAME')
    if '.' not in netloc:
        netloc = _LOCALHOST
    if port and port != '80':
        netloc += ':' + port
    if path is None:
        path = env('PATH_INFO')
    if path is None:
        path = ''
    elif path.endswith('index.py'):
        path = path.rsplit('/', 1)[0] + '/'
    scheme = 'https' if (port and int(port) == 443) else 'http'
    return urlunsplit([scheme, netloc, path, '', ''])


def is_cgi(self):
    """Monkey patch for CGIHTTPRequestHandler.is_cgi()."""
    if _NAME in self.path:
        self.cgi_info = '', self.path[1:]
        return True
    return False


def main():
    """Start web browser or print response."""
    if os.getenv('SERVER_NAME'):
        print("Content-type: text/html\n\n")
        print(response(cgi.FieldStorage()))
    else:
        import webbrowser
        import cgitb
        cgitb.enable()
        if sys.version_info[0] == 2:
            from BaseHTTPServer import HTTPServer
            from CGIHTTPServer import CGIHTTPRequestHandler
        else:
            from http.server import HTTPServer, CGIHTTPRequestHandler
        CGIHTTPRequestHandler.is_cgi = is_cgi
        _url = "http://localhost:%i/%s" % (_LOCALPORT, _NAME)
        print("Serving CGI script at", _url)
        webbrowser.open(_url)
        HTTPServer((_LOCALHOST, _LOCALPORT),
                   CGIHTTPRequestHandler).serve_forever()


_LOCALHOST = "0.0.0.0"
_LOCALPORT = 9000
_NAME = os.path.split(__file__)[-1]

if __name__ == "__main__":
    main()