#!/usr/bin/env python3
# Copyright (C) 2016-2017 Florian Festi
#
#   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/>.

import sys
import argparse
import cgi
import tempfile
import os.path
import threading
import time
import codecs
import mimetypes
import re

# Python 2 vs Python 3 compat
try:
    from urllib.parse import unquote_plus
except ImportError:
    from urllib import unquote_plus


from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server
import wsgiref.util

try:
    import boxes.generators
except ImportError:
    sys.path.append(os.path.dirname(__file__) + "/..")
    import boxes.generators

class FileChecker(threading.Thread):
    def __init__(self, files=[], checkmodules=True):
        super(FileChecker, self).__init__()
        self.checkmodules = checkmodules
        self.timestamps = {}
        for path in files:
            self.timestamps[path] = os.stat(path).st_mtime
        if checkmodules:
            self._addModules()

    def _addModules(self):
        for name, module in sys.modules.items():
            path = getattr(module, "__file__", None)
            if not path:
                continue
            if path not in self.timestamps:
                 self.timestamps[path] = os.stat(path).st_mtime

    def filesOK(self):
        if self.checkmodules:
            self._addModules()
        for path, timestamp in self.timestamps.items():
            try:
                if os.stat(path).st_mtime != timestamp:
                    return False
            except FileNotFoundError:
                return False
        return True

    def run(self):
        while True:
            if not self.filesOK():
                os.execv(__file__, sys.argv)
            time.sleep(1)

class ArgumentParserError(Exception): pass

class ThrowingArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        raise ArgumentParserError(message)
boxes.ArgumentParser = ThrowingArgumentParser # Evil hack

class BServer:
    def __init__(self):
        self.boxes = {b.__name__ : b() for b in boxes.generators.getAllBoxGenerators().values() if b.webinterface}
        self.boxes['TrayLayout2'] = boxes.generators.traylayout.TrayLayout2(self, webargs=True)
        self.groups = boxes.generators.ui_groups
        self.groups_by_name = boxes.generators.ui_groups_by_name

        for name, box in self.boxes.items():
            self.groups_by_name.get(box.ui_group,
                                    self.groups_by_name["Misc"]).add(box)

        self.staticdir = os.path.join(os.path.dirname(__file__), '../static/')

    def arg2html(self, a, prefix):
        name = a.option_strings[0].replace("-", "")
        if isinstance(a, argparse._HelpAction):
            return ""
        viewname = name
        if prefix and name.startswith(prefix + '_'):
            viewname = name[len(prefix)+1:]

        row = """<tr><td>%s</td><td>%%s</td><td>%s</td></tr>\n""" % \
              (viewname, a.help or "")
        if (isinstance(a, argparse._StoreAction) and
            hasattr(a.type, "html")):
            input = a.type.html(name, a.default)
        elif a.dest == "layout":
            val = a.default.split("\n")
            input = """<textarea name="%s" cols="%s" rows="%s">%s</textarea>""" % \
                    (name, max((len(l) for l in val))+10, len(val)+1, a.default)
        elif a.choices:
            options = "\n".join(
                ("""<option value="%s"%s>%s</option>""" %
                 (e, ' selected="selected"' if e == a.default else "",
                  e) for e in a.choices))
            input = """<select name="%s" size="1">\n%s</select>\n""" % (name, options)
        else:
            input =  """<input name="%s" type="text" value="%s">""" % \
            (name, a.default)

        return row % input

    scripts = """
    <script type="text/javascript">
    function showHide(id) {
       var e = document.getElementById(id);
       if(e.style.display == null || e.style.display == "none") {
         e.style.display = "block";
       } else {
         e.style.display = "none";
      }
    }
    function hideargs() {
     for ( i=0; i<%i; i++) {
       showHide(i);
     }
    }
    </script>
"""

    def args2html(self, name, box, action=""):
        result = ["""<html><head><title>Boxes - """, name, """</title>
    <link rel="stylesheet" href="https://necolas.github.io/normalize.css/4.1.1/normalize.css" type="text/css" />
    <link rel="stylesheet" href="static/self.css" type="text/css" />
""", self.scripts % (len(box.argparser._action_groups)-3), """
    <script>
   </script>
</head>
<body onload="hideargs()">


<div class="container" style="background-color: #FFF8EA;">
<div style="float: left;">
<a href="./"><h1>Boxes.py</h1></a>
</div>
<div style="width: 150px; float: right;">
<img alt="self-Logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Gray_shaded_3D_cube.svg/2000px-Gray_shaded_3D_cube.svg.png" width="150px" >
</div>
<div>
<div class="clear"></div>
<hr />
<h2 style="margin: 0px 0px 0px 20px;" ><span>""", name, """</h2>
        <p>""", box.__doc__ or "", """</p>
<form action="%s" method="GET" target="_blank">
        """ % (action)]
        groupid = 0
        for group in box.argparser._action_groups[3:] + box.argparser._action_groups[:3]:
            if not group._group_actions:
                continue
            prefix = getattr(group, "prefix", None)
            result.append('''<h3 onclick="showHide(%s)">%s</h3>\n<table id="%s">\n''' % (groupid, group.title, groupid))
            for a in group._group_actions:
                if a.dest in ("input", "output"):
                    continue
                result.append(self.arg2html(a, prefix))
            result.append("</table>")
            groupid += 1
        result.append("""
<p><button name="render" value="1">Generate</button></p>
</form>

</div>
<!--
<div style="width: 5%; float: left;"></div>
<div style="width: 35%; float: left;">
<img alt="sample" src="examples/box.svg" width="300px" >
<span id="sicherheitshinweise">hier kommt dann der AJAX-Inhalt</span>
</div>
-->
<div class="clear"></div>
<hr />
</div>
</div>

<div class="footer container">
<ul>
 <li><a href="https://github.com/florianfesti/boxes">Get Source at GitHub</a></li>
 <li><a href="http://florianfesti.github.io/boxes/html/index.html">Documentation and API Description</a></li>
 <li><a href="https://hackaday.io/project/10649-boxespy">Hackaday.io Project Page</a></li>
</ul>
</div>




</body>
</html>
""")
        return (s.encode("utf-8") for s in result)
        
    def menu(self):
        
        result = ["""<html>
<head><title>Boxes.py</title>
    <link rel="stylesheet" href="https://necolas.github.io/normalize.css/4.1.1/normalize.css" type="text/css" />
    <link rel="stylesheet" href="static/self.css" type="text/css" />
    <script>
	function change(group, img_link){
	  document.getElementById("sample-"+group).src = img_link;
	  document.getElementById("sample-"+group).style.height = "auto";
	}
				
	function changeback(group){
	  document.getElementById("sample-" + group).src= "static/nothing.png";
	  document.getElementById("sample-" + group).style.height= "0px";
	}
        </script>""", self.scripts % len(self.groups), """
</head>
<body onload="hideargs()">
<div class="container" style="background-color: #FFF8EA;">
<div style="width: 70%; float: left;">
<h1>Boxes.py</h1>
<p>
Create boxes and more with a laser cutter!
</p>
<p>

        <a href="https://hackaday.io/project/10649-boxespy">Boxes.py</a> is an <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">Open Source</a> box generator written in <a href="https://www.python.org/">Python</a>. It features both finished parametrized generators as well as a Python API for writing your own. It features finger and (flat) dovetail joints, flex cuts, holes and slots for screws, hinges, gears, pulleys and much more.
</p>


</div>
<div style="width: 30%; float: left;">
<img alt="self-Logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Gray_shaded_3D_cube.svg/2000px-Gray_shaded_3D_cube.svg.png" width="300px" >
</div>
<div>
<div class="clear"></div>
<hr />
<div style="width: 100%">
""" ]
        for nr, group in enumerate(self.groups):
            result.append('''<h3 onclick="showHide('%s')">%s</h3>\n<div id="%s">\n<ul>\n''' % (nr, group.title, nr))
            result.append("""
            <div style="width: 20%%; float: right;">
            <img width="100%%" id="sample-%s" src="static/nothing.png" alt="" />
            </div>""" % (group))
            for box in group.generators:
                name = box.__class__.__name__
                if name in ("TrayLayout2", ):
                    continue
                docs = ""
                if box.__doc__:
                    docs = " - " + box.__doc__
                result.append("""  <li onmouseenter="change('%s', 'static/samples/%s.jpg')" onmouseleave="changeback('%s')"><a href="%s">%s</a>%s</li>\n""" % (
                    group, name, group, name, name, docs))
            result.append("</ul>\n</div>\n")
        result.append("""
</div>

<div style="width: 5%; float: left;"></div>
<div class="clear"></div>
<hr />
</div>
</div>

<div class="footer container">



<ul>
 <li><a href="https://github.com/florianfesti/boxes">Get Source at GitHub</a></li>
 <li><a href="http://florianfesti.github.io/boxes/html/index.html">Documentation and API Description</a></li>
 <li><a href="https://hackaday.io/project/10649-boxespy">Hackaday.io Project Page</a></li>
</ul>

</div>

 
</body>
</html>
""")
        return (s.encode("utf-8") for s in result)


    def errorMessage(self, name, e):
        return [
            b"""<html><head><title>Error generating""", name.encode(),
            b"""</title><head>
<body>
<h1>An error occurred!</h1>
<p>""", str(e).encode(), b"""</p>
</body>
</html>
""" ]

    def serveStatic(self, environ, start_response):
        filename = environ["PATH_INFO"][len("/static/"):]
        path = os.path.join(self.staticdir, filename)
        print(filename, path)
        if (not re.match(r"[a-zA-Z0-9_/-]+\.[a-zA-Z0-9]+", filename) or
            not os.path.exists(path)):
            start_response("404 Not Found", [('Content-type', 'text/plain')])
            return [b"Not found"]

        type_, encoding = mimetypes.guess_type(filename)
        if encoding is None:
            encoding = "utf8"

        start_response("200 OK", [('Content-type', "%s; charset=%s" % (type_, encoding))])

        f = open(path, 'rb')
        return environ['wsgi.file_wrapper'](f, 512*1024)

    def serve(self, environ, start_response):

        if environ["PATH_INFO"].startswith("/static/"):
            return self.serveStatic(environ, start_response)

        status = '200 OK'
        headers = [('Content-type', 'text/html; charset=utf-8')]

        d = cgi.parse_qs(environ['QUERY_STRING'])
        name = environ["PATH_INFO"][1:]

        box = self.boxes.get(name, None)
        if not box:
            start_response(status, headers)
            return self.menu()
      
			
        args = ["--"+unquote_plus(arg) for arg in environ['QUERY_STRING'].split("&")]
        if "--render=1" not in args:
            start_response(status, headers)
            return self.args2html(name, box)
        else:
            args = [a for a in args if a != "--render=1"]
            try:
                box.parseArgs(args)
            except (ArgumentParserError) as e:
                start_response(status, headers)
                return self.errorMessage(name, e)
            if name == "TrayLayout":
                start_response(status, headers)
                box.fillDefault(box.x, box.y)
                self.boxes["TrayLayout2"].argparser.set_defaults(layout=str(box))
                return self.args2html(
                    name, self.boxes["TrayLayout2"], action="TrayLayout2")
            if name == "TrayLayout2":
                try:
                    box.parse(box.layout.split("\n"))
                except Exception as e:
                    raise
                    start_response(status, headers)
                    return self.errorMessage(name, e)

            start_response(status,
                           box.formats.http_headers.get(
                               box.format,
                               [('Content-type', 'application/unknown; charset=utf-8')]))
            fd, box.output = tempfile.mkstemp()
            box.render()
            result = open(box.output).readlines()
            os.remove(box.output)
            os.close(fd)
            return (l.encode("utf-8") for l in result)

if __name__=="__main__":
    fc = FileChecker()
    fc.start()
    boxserver = BServer()
    httpd = make_server('', 8000, boxserver.serve)
    print("Serving on port 8000...")
    httpd.serve_forever()
else:
    application = BServer().serve

	
