#!python
# Copyright (C) 2019, Ayan Chakrabarti <ayan.chakrabarti@gmail.com>

# MIT License
# -----------
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.


# Main index + JS

html_index='''
<!DOCTYPE html>
<html>
  <head>
    <style>
      button {background-color: #172; border: none; color: white; padding: 3px 9px; text-align: center; display: inline-block; border-radius: 2px; cursor: pointer;}
      button:hover {background-color: #a45;}
      button:disabled {background-color: #bcb; color: #787; cursor: not-allowed;}
      select {background-color: #fff; border: 0.5px solid #aaa; padding: 3px; border-radius: 5px;}
      body {
        background: #536976; background: linear-gradient(to right, #292E49, #536976); overflow-y: hidden;
        margin: 0px; margin-top: 0.5vh; padding: 0px; height: 99vh;
      }
      
      .main {height: 100%; padding: 0px; width: 1250px; margin: 0px auto;}
      .left {height: 100%; float: left; width: 820px; padding: 0; margin: 0; background: #fff;}
      .right {float: right; width: 390px; max-height: 99vh; margin: 0 10px; overflow-y: auto; padding: 0px 10px;  background: #9a9; border-radius: 10px;}
      
    </style>
  </head>
  <body>
    <div class="main">
      <div class="left">
	<iframe id="pdfv" src="" style="display: block; width:100%; height:100%; border: 0px;"> </iframe>
      </div>
      <div class="right">
	<p><form method="get" action="/export" target=_blank>
	  <span style="font-size: 0.8em;">
            (RAW .md ?<input type="checkbox" name="raw" value="1">)&nbsp;
            Sort:&nbsp;</span><select style="width: 10em; font-size: 0.7em;" name="order">
	    <option value="0" selected="selected">By ID</option>
	    <option value="1">By Score &uarr;</option>
	    <option value="2">By Score &darr;</option>
	  </select>
	  <span style="font-size: 0.8em;">&nbsp;&nbsp;</span><button id='expbtn' type="submit" accesskey="x">E<u>x</u>port</button>
	</form></p>
	<hr />

	<p style="font-size: 0.8em;">ID:&nbsp;<select id="usel" style="width: 320px;" accesskey="l" onchange="javascript:selectUser(parseInt(this.value));">
	</select></p>

	<p>
	  <select id="sglst" style="width: 3em;" onclick="javascript:addSugg(this.value);" onblur="javascript:this.value = -1;"></select>
	  <span style="font-size: 0.8em;">Comments</span>
	</p>
	<p>
            <textarea id="comm" style="width: 98%; height: 50vh; margin: auto;" oninput="javascript:doChg();"></textarea>
	</p>

	<p style="font-size: 0.8em; margin-top: 0.5em;">Score: <input type="text" id='score'  oninput="javascript:doChg();" value="" style="width: 70px;"></p>
	
	<p style="margin-top: 1em;">
	  <button onclick="javascript:selectUser(flid+1);" accesskey="n"><u>N</u>ext</button>&nbsp;&nbsp;
	  <button onclick="javascript:selectUser(flid-1);" accesskey="p"><u>P</u>rev</button>&nbsp;&nbsp;&nbsp;
	  <button id='svbtn' disabled onclick="javascript:upd();" accesskey="s"><u>S</u>ave</button>&nbsp;&nbsp;
	  <button id='undobtn' disabled onclick="javascript:unDo();" accesskey="u"><u>U</u>ndo</button>
	</p>
      </div>
    </div>

<script>

var data = {};
var flkeys;
var flid = -1;
var usel = document.getElementById('usel');
var sglst = document.getElementById('sglst');
var score = document.getElementById('score');
var comm = document.getElementById('comm');
var pdfv = document.getElementById('pdfv');
var svbtn = document.getElementById('svbtn');
var expbtn = document.getElementById('expbtn');
var undobtn = document.getElementById('undobtn');

var chgd = false;

var sugg = [];

function makeSugg() {
    sugg = [];
    for(i = 0; i < flkeys.length; i++)
	sugg = sugg.concat(
	    data[flkeys[i]].comments
		.split('\\n').map(x => x.trim())
		.filter( (ei,i,a) => (ei.length > 0))
	);
    sugg2a = Array.from(new Set(sugg)); sugg2b = sugg2a.map(x => [x,0]);
    for(i = 0; i < sugg.length; i++) {
        i2 = sugg2a.indexOf(sugg[i]);
        sugg2b[i2][1] = sugg2b[i2][1] + 1;
    }
    sugg = sugg2b.sort((a,b) => b[1]-a[1]).map(x => x[0]);

    html = '<option value="-1">+</option>';
    for(i = 0; i < sugg.length; i++) {
	si = sugg[i];
	if(si.length > 70)
	    si = si.substr(0,58) + ' .... ' + si.substr(-6);
	html=html+'<option value="'+i+'">'+si+'</option>';
    }
    sglst.innerHTML=html;
}


function doChg() {
    chgd = true; svbtn.disabled = false; expbtn.disabled = true; undobtn.disabled = false;
}

function unDo() {
    score.value = data[flkeys[flid]].score;
    comm.value =  data[flkeys[flid]].comments;
    chgd = false; svbtn.disabled = true; expbtn.disabled = false; undobtn.disabled = true;
}

function upd() {
    if(flid == -1 || !chgd)
	return;
    k = flkeys[flid];
    data[k].comments = comm.value; data[k].score = score.value;

    xhr = new XMLHttpRequest();
    xhr.open("POST", "/save");
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.send(JSON.stringify(data));
      
    makeSugg();
    chgd = false; svbtn.disabled = true; expbtn.disabled = false; undobtn.disabled = true;
}

function addSugg(i) {
    if(i < 0) return;
    comm.value = comm.value.trimEnd();
    if(comm.value.length > 0) comm.value = comm.value + '\\n';
    comm.value = comm.value + sugg[i]; doChg();
    sglst.value = -1;
}

function initUsers() {
    html='';
    for(i = 0; i < flkeys.length; i++)
	html=html+'<option value="'+i+'">'+flkeys[i]+'</option>';
    usel.innerHTML=html;
}

function selectUser(id) {
    if(id < 0) id = 0;
    if(id >= flkeys.length) id = flkeys.length-1;
    upd();
    if(id == flid)
	return;
    flid = id;
    usel.selectedIndex = flid;
    score.value = data[flkeys[flid]].score;
    comm.value =  data[flkeys[flid]].comments;
    pdfv.src = "/sub?q="+flkeys[flid];
}

xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
    if(this.readyState == 4) {
	data = JSON.parse(this.responseText);
	flkeys = Object.keys(data).sort();
	initUsers(); selectUser(0); makeSugg();
    }
};
xhr.open("GET", "/load");
xhr.send();

window.onunload = function () {
    navigator.sendBeacon("/close","")
}
            
</script>
  </body>
</html>
'''

######## Python App code

import glob, os, sys, argparse, random, threading, webbrowser, flask
import markdown as md
request = flask.request
json = flask.json

p = argparse.ArgumentParser()
p.add_argument('-ext',default='pdf',help='Extension of files to search for as submissions (e.g., html). Default pdf.')
p.add_argument('-nb',help='Do not start browser, or close app when browser window closed.',action="store_true")
p.add_argument('jsfile',help='Name of JSON file that comments and scores will be saved in.')
p = p.parse_args()

dext = p.ext
ifbrow = not p.nb
ddir = os.getcwd()+'/'
jsfile = ddir+p.jsfile


# Init db
fkeys = [u[:-(1+len(dext))] for u in sorted(glob.glob('*.'+dext))]
try:
    with open(jsfile) as f:
        db = json.load(f)
except:
    db = {}
dbkeys = list(db.keys())
for k in fkeys:
    if k not in dbkeys:
        db[k] = {'score': '', 'comments': ''}


################ Markdown export

mdfmt=['''
<html><head><style>body { margin: 0;}.content {-webkit-text-size-adjust:100%;text-size-adjust:100%;color:#333;font-family:"Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif; font-size:16px;line-height:1.6;word-wrap:break-word; min-width: 200px;max-width: 800px;margin: 0 auto;padding: 45px; padding-top: 15px; font-size: 0.8em; border-left: 1px solid #ddd; border-right: 1px solid #ddd;}hr{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #ddd;} hr:before{display:table;content:"";} hr:after{display:table;clear:both;content:"";}h1, h2, h3,.markdown-body h4, h5, h6 {margin-top:15px;margin-bottom:15px;line-height:1.1;}
h1{font-size:30px;} h2{font-size:21px;} h3{font-size:16px;} h4{font-size:14px;} h5{font-size:12px;} h6{font-size:11px;}ul {padding-left: 15px; margin-left: 0px;}ol {padding-left: 15px; margin-left: 0px;}</style></head><body><div class="content">
''','</div></body></html>']

def m2html(mark):
    resp = mdfmt[0] + md.markdown(mark) + mdfmt[1]
    return resp

def getmark(order,raw):
    def num(s):
        try:
            return float(s)
        except:
            return float(0)

    keys = [[k, num(db[k]['score'])] for k in fkeys]
    if order == '0':
        keys = sorted(keys,key=lambda x: x[0])
    if order == '1':
        keys = sorted(keys,key=lambda x: x[1])
    elif order == '2':
        keys = sorted(keys,key=lambda x: -x[1])

    mark = ''
    for k2 in keys:
        k = k2[0]
        mark = mark + '## ' + k + ': ' + db[k]['score'] + '\n'
        mark = mark + db[k]['comments'] + '\n\n---\n'

    if raw:
        return flask.Response(mark,mimetype='text/markdown')
    else:
        return m2html(mark)



################ Simple Flask endpoints

app = flask.Flask(__name__)
@app.route('/', methods=['GET'])
def index():
    return html_index

@app.route('/sub', methods=['GET'])
def pdf():
    key=request.args.get('q')
    return flask.send_file(ddir+key+'.'+dext,cache_timeout=0)
    
@app.route('/load', methods=['GET'])
def load():
    sdb = {}
    for k in fkeys:
        sdb[k] = db[k]
    return json.jsonify(sdb)

@app.route('/save', methods=['POST'])
def save():
    udb = json.loads(request.data)
    db.update(udb)
    with open(jsfile,'w') as f:
        json.dump(db,f)
    return ""

@app.route('/export',methods=['GET'])
def export():
    order = request.args.get('order')
    raw = 'raw' in list(request.args.keys())
    return getmark(order,raw)

@app.route('/close', methods=['GET','POST'])
def close():
    if ifbrow:
        sys.exit(0)
    return ""


### Start app
wport = 5000 + random.randint(0,999)
if ifbrow:
    threading.Timer(1.5, lambda: webbrowser.open("http://127.0.0.1:{0}".format(wport)) ).start()
    sys.stderr.write('Starting browser automatically\n')
app.run(port=wport)
    
