package caddyhugo

import (
	"encoding/base64"
	"fmt"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"git.stephensearles.com/stephen/acedoc"

	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/spf13/hugo/hugolib"
)

type Content struct {
	Title         string
	Path          string
	Date, Lastmod time.Time
}

func (t tmplData) Content() ([]Content, error) {
	var files []Content

	pages := t.HugoSites.Pages().ByDate()
	for _, page := range pages {
		if page.Kind == hugolib.KindPage {
			files = append(files, Content{
				Title:   page.Title,
				Path:    path.Join("content", page.Source.Path()),
				Date:    page.Date,
				Lastmod: page.Lastmod,
			})
		}
	}

	return files, nil
}

func (t tmplData) ContentTypes() ([]string, error) {
	nameMap := map[string]struct{}{"default": struct{}{}}

	names, err := t.contentTypes(path.Join(t.Site.Root, "archetypes"))
	if err != nil {
		return nil, err
	}
	for _, name := range names {
		nameMap[name] = struct{}{}
	}

	names, err = t.contentTypes(path.Join(t.Site.Root, "themes", "hugo-theme-minos", "archetypes"))
	if err != nil {
		return nil, err
	}
	for _, name := range names {
		nameMap[name] = struct{}{}
	}

	var out []string
	for name := range nameMap {
		out = append(out, name[:len(name)-len(filepath.Ext(name))])
	}

	return out, nil
}

func (t tmplData) contentTypes(dir string) ([]string, error) {
	layoutDir, err := os.Open(path.Join(t.Site.Root, "archetypes"))
	if err != nil {
		fmt.Println("opening layout dir", err)
		return nil, err
	}
	defer layoutDir.Close()

	names, err := layoutDir.Readdirnames(0)
	if err != nil {
		fmt.Println("reading dir", err)
		return nil, err
	}

	return names, nil
}

type tmplData struct {
	Site *httpserver.SiteConfig
	R    *http.Request
	CaddyHugo
	Doc    *acedoc.Document
	docref *docref
}

func (t tmplData) LoadContent() (string, error) {
	return t.Doc.Contents(), nil
}

func baseNoExt(name string) string {
	base := path.Base(name)
	return base[:len(base)-len(path.Ext(base))]
}

func (t tmplData) IframeSource() string {
	name := baseNoExt(t.docref.name)
	ctype := baseNoExt(path.Dir(t.docref.name))
	if ctype == "content" {
		return fmt.Sprintf("/hugo/draft/%s/%s/", base64.RawURLEncoding.EncodeToString([]byte(t.docref.name)), strings.Replace(name, " ", "-", -1))
	}
	return fmt.Sprintf("/hugo/draft/%s/%s/%s/", base64.RawURLEncoding.EncodeToString([]byte(t.docref.name)), ctype, strings.Replace(name, " ", "-", -1))
}

var EditPage = `<html>
<head>
<script src="/hugo/simplemde.js"></script>
<style type="text/css" media="screen">
#editor-wrapper { 
	position: absolute;
	top: 50px;
	right: 0;
	bottom: 150px;
	left: 40%;
}
#draft {
	position: absolute;
	top: 50px;
	right: 60%;
	bottom: 0;
	left: 0;

}
#draft > iframe {
	height: 100%;
	width: 100%;
	border: none;
}
a {
	text-decoration: underline;
	text-decoration-style: dotted;
	cursor: pointer;
}
</style>
<link rel="stylesheet" href="/hugo/simplemde.css" />
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/moment"></script>


<body>
<div id="container" >
	<div id="header">
		<div id="lastSaved">
			<span v-if="ltime >serverLtime && (sendQueue.length > 0 || sentRecently.length > 0)">last saved ${ lastSaved.from(now) }, saving</span>
			<span v-else>saved</span>
			<span v-if="connectionError">, ${connectionError}</span>
		</div>
		<div>
			<a id="sideview-toggle-media">media</a>
			<a id="sideview-toggle-draft">draft</a>
		</div>
	</div>
	<div id="editor-wrapper">
		<textarea id="editor">{{ .LoadContent }}</textarea>
	</div>
	<div id="draft"><iframe src="{{ .IframeSource }}">Loading draft...</iframe></div>
</div>
<script>

    var iframe = document.querySelector("#draft > iframe");

    document.onclick = function (event) {
	    var iframe = document.querySelector("#draft > iframe");
	    switch (event.target.id) {
	    case "sideview-toggle-media":
		    iframe.src = "/hugo/media";
		    break;
	    case "sideview-toggle-draft":
		    iframe.src = "{{ .IframeSource }}";
		    break;
	    }
    }

    var uiBindings = {
	    ltime: 0,
	    serverLtime: 0,
	    lastSaved: moment(),
	    now: moment(),
	    connectionError: null,
	    sendQueue: [],
	    sentRecently: [],
    };

    var app = new Vue({
	    el: "#container",
	    data: uiBindings,
	    delimiters: ["${", "}"],
    });

    function getLtime() {
	    uiBindings.ltime++
	    return uiBindings.ltime
    }

    function observeServer(l) {
	    uiBindings.serverLtime = l;
	    while (uiBindings.sentRecently.length > 0 && uiBindings.sentRecently[0].ltime < l) {
		    uiBindings.sentRecently.pop();
	    }
	    observe(l);
    }

    function observe(l) {
	 if (l > uiBindings.ltime) { 
		 uiBindings.now = moment();
		 uiBindings.lastSaved = moment();
		 uiBindings.ltime = l;
	 }
    }

    var editorElem = document.getElementById("editor");
    var editor = new SimpleMDE({element: editorElem, forceSync: true});


    // Create WebSocket connection.
    var socket = connect();


    const sawChangesBumpsTo = 10;

    var sawChanges = -1;
    window.setInterval(function () {
	    if (sawChanges >= 0) {
		    sawChanges--;
		    if (sawChanges == 0) {
			    iframe.contentWindow.location.reload();
		    }
	    }
	    uiBindings.now = moment();
	    if (uiBindings.connectionError) {
		    socket = connect();
	    } else if (uiBindings.sendQueue.length > 0) {
		    var ltime = getLtime();
		    socket.send(JSON.stringify({
			    "deltas": uiBindings.sendQueue,
			    "ltime": ltime,
		    }));
		    uiBindings.sentRecently.push({
			    "ltime": ltime,
			    "sent": uiBindings.sendQueue,
		    });
		    uiBindings.sendQueue = [];
	    }
    }, 500);

    function connect() {
	    const socket = new WebSocket((location.protocol == "https:" ? 'wss://' : 'ws://') + location.host + location.pathname);

	    // Listen for messages
	    socket.addEventListener('message', function (event) {
		 var message = JSON.parse(event.data);
		 observeServer(message.ltime);

		 var deltas = [];
		 deltas.push.apply(deltas, message.deltas);

		 deltas.forEach(function(aceDelta) {
			 var cmDelta = aceDeltaToCM(aceDelta)

			 var content = ""
			 var to = {
				 line: aceDelta.start.row,
				 ch: aceDelta.start.column,
			 }

			 if (aceDelta.action == "insert") {
				 content = aceDelta.lines.join("\n");
				 to = null;
			 }

			 editor.codemirror.doc.replaceRange(content, cmDelta.from, to, "dontreflect");
			 sawChanges = sawChangesBumpsTo;
		 })
	    });

	    socket.addEventListener('open', function () {
		    uiBindings.connectionError = null;
	    });

	    socket.addEventListener('close', function () {
		    if (!uiBindings.connectionError) {
			    getLtime();
			    uiBindings.connectionError = "server connection closed, reconnecting...";
		    }
	    });

	    socket.addEventListener('error', function (err) {
		    if (!uiBindings.connectionError) {
			    uiBindings.connectionError = err;
		     	    getLtime();
		    }
		    console.log(err);
	    });


	    return socket;
    }

    editor.codemirror.on("change", function (cm, cmDelta) {
		    if (cmDelta.origin == "dontreflect") {
			    return;
		    }

		    var aceDelta = cmDeltaToAce(cmDelta);
		    console.log(cmDelta, "=>", aceDelta)

		    sawChanges = sawChangesBumpsTo;
		    uiBindings.sendQueue.push.apply(uiBindings.sendQueue, aceDelta);
    })

    function cmDeltaToAce(cmDelta) {
	    var isRemove =  (cmDelta.removed.length > 0 && cmDelta.removed[0].length > 0) || cmDelta.removed.length > 1;
	    var lines = isRemove ? cmDelta.removed : cmDelta.text;
	    var aceDelta = {
		    action: isRemove ? "remove" : "insert",
		    lines: lines,
		    start: {
			    row: cmDelta.from.line,
			    column: cmDelta.from.ch,
		    },
		    end: {
			    row: cmDelta.from.line + (isRemove ? lines.length - 1 : lines.length - 1 ),
			    column: lines[lines.length-1].length,
		    }
	    };

	    if (aceDelta.start.row == aceDelta.end.row) {
		    aceDelta.end.column += cmDelta.from.ch;
	    }

	    if (false && isRemove && aceDelta.start.row == aceDelta.end.row) {
		    var origStart = aceDelta.start;
		    aceDelta.start = aceDelta.end;
		    aceDelta.end = origStart;
		    aceDelta.start.column += cmDelta.from.ch;
	    }

	    if (isRemove && ((cmDelta.text.length > 0 && cmDelta.text[0].length > 0) || cmDelta.text.length > 1)) {
		    cmDelta.removed = [""];
		    var ret = [aceDelta];
		    ret.push.apply(ret, cmDeltaToAce(cmDelta));
		    return ret;
	    }

	    return [aceDelta];
    }

    function aceDeltaToCM(aceDelta) {

	    var cmDelta = {
		    text: [],
		    removed: [],
		    from: {
			    line: aceDelta.start.row,
			    ch: aceDelta.start.column,
		    },
		    to: {
			    // cm deltas are weird. to refers to the selection end, which 
			    // with a simple blinking cursor with no selection, is always 
			    // the same as from
			    line: aceDelta.start.row,
			    ch: aceDelta.start.column,
		    },
	    }

	    if (aceDelta.action == "remove") {
		    var origStart = aceDelta.start;
		    aceDelta.start = aceDelta.end;
		    aceDelta.end = origStart;

		    cmDelta.removed = aceDelta.lines
		    cmDelta.text = [""]
	    } else {
		    cmDelta.text = aceDelta.lines
		    cmDelta.removed = [""]
	    }

	    return cmDelta;
    }
</script>
</body>
</html>`

var AdminPage = `<html><body>not implemented</body></html>`

var AuthorPage = `<html>
<head>
</head>
<body>
{{ $timeFormat := "Jan _2 15:04:05" }}
	<p>Create content:</p>
	<form action="/hugo/edit/new" method="POST">
		<label>Name: <input type="text" name="name" /></label>
		<select name="type">
		    {{- range .ContentTypes }}
			<option value="{{ . }}">{{ . }}</option>
		    {{- end }}
		</select>
		<input type="submit" />
	</form>

	<p>Edit content:</p>
	<table>{{ range .Content }}
	<tr><td><a href="/hugo/edit/{{ .Path }}">{{ .Title }}</a></td><td>{{ .Date.Format $timeFormat }} (last modified {{ .Lastmod.Format $timeFormat }})</td></tr>
	    {{- end }}
	</table>
</body>
</html>`

func UploadPage(elemName string) string {
	return fmt.Sprintf(`
	<input type="file" style="display: hidden;" id="%s" />
	<div id="%s_dropzone" ondrop="dropHandler(event);" ondragover="draghandler(event);" ondragenter="draghandler(event);" ondragleave="draghandler(event);" style="background-color: rgba(0,0,0,0.5); visibility: hidden; opacity:0; position: fixed; top: 0; bottom: 0; left: 0; right: 0; width: 100%; height: 100%; ; transition: visibility 175ms, opacity 175ms; z-index: 9999999;"></div>
	<script>
document.addEventListener("DOMContentLoaded", function () {
	var fileInput = document.getElementById('%s');
	var dropzone = document.getElementById('%s_dropzone');

	fileInput.onchange = function () {
		var formData = new FormData();
		fileInput.files.forEach(function (file) {
			formData.append(file.name, file);
		});
		upload(formData);
	}

	var lastTarget = null;

	window.addEventListener("dragenter", function(e)
	{
		lastTarget = e.target; // cache the last target here
		// unhide our dropzone overlay
		dropzone.style.visibility = "";
		dropzone.style.opacity = 1;
	});

	window.addEventListener("dragleave", function(e)
	{
		// this is the magic part. when leaving the window,
		// e.target happens to be exactly what we want: what we cached
		// at the start, the dropzone we dragged into.
		// so..if dragleave target matches our cache, we hide the dropzone.
		if(e.target === lastTarget)
		{
			dropzone.style.visibility = "hidden";
			dropzone.style.opacity = 0;
		}
	});

});

	function draghandler(evt) {
		evt.preventDefault();
	}

	function dropHandler(evt) {
		evt.preventDefault();

		var files = evt.dataTransfer.files;
		var formData = new FormData();

		for (var i = 0; i < files.length; i++) {
			formData.append(files[i].name, files[i]);
		}

		upload(formData);
		return false;
	}

	function upload(formData) {
		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function(e) {
			if ( 4 == this.readyState ) {
				window.location.reload(true);
			}
		}
		xhr.open('POST', '/hugo/upload');
		xhr.send(formData);
	}

</script>
`, elemName, elemName, elemName, elemName, elemName, elemName)
}
