#!/usr/bin/env python
# -*- coding: utf-8 -*-
# created 21Jul2021, 00:50AM -0300
import os
import sys
import http.server
import socket
import urllib.parse as urlparse
from html import escape
import zipfile
import io


# extensions for supported images/videos
imgext = ('.jpg', '.jpeg', '.jfif', '.png', '.avif', '.jxl', '.gif', '.webp')
vidext = ('.mp4', '.mkv', '.webm')

html = (
    # start
    '''<!DOCTYPE html><html>
<head><meta charset='UTF-8'></head>
<body>
''',
    # end
    '''
</body></html>
''',
    # sort, zip
    '''
<hr><b>Sort by</b><br>
<span><a href='?s=6'>Size (largest first)</a> |
<a href='?s=8'>Last modified (recent first)</a> |
<a href='?s=10&r=1'>Name (A to Z)</a></span><br>
<span><a href='?s=6&r=1'>Size (smallest first)</a> |
<a href='?s=8&r=1'>Last modified (oldest first)</a> |
<a href='?s=10'>Name (Z to A)</a></span><br><br>
<a href='?zip=1'>download as zip</a><hr>
''',
    # no files
    '''<h1>No supported file types</h1>
<p>this is where images/videos would show up. if there was some!<br><br>add
images/videos to the selected folder and refresh the page to see them</p>'''
)


# taken from github.com/simon-budig/woof
def find_ip():
    candidates = []
    for test_ip in ('192.0.2.0', '198.51.100.0', '203.0.113.0'):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect((test_ip, 80))
        ip_addr = s.getsockname()[0]
        s.close()
        if ip_addr in candidates:
            return ip_addr
    candidates.append(ip_addr)

    return candidates[0]


def sort(files, num=8, reverse=True):

    # if num is an os.stat item, sort by stat, otherwise sort alphabetically
    if num < 10:
        fdict = {}
        for f in files:
            fdict |= {
                f: os.stat(f)[num]
            }
        return dict(sorted(fdict.items(), key=lambda x: x[1], reverse=reverse)).keys()
    else:
        files.sort(reverse=reverse)
        return files

def handle_queries(path, query):
    queries = urlparse.parse_qs(path.split('?', 1)[1])

    qlist = []
    for q in query:
        if q in queries:
            qlist.append(int(queries[q][0]))
        else:
            qlist.append(False)

    return qlist


class coolHandler(http.server.SimpleHTTPRequestHandler):

    def generate_zip(self, files):
        self.send_header('Content-type', 'application/zip')
        fd = io.BytesIO()
        flist = []

        for f in files:
            flower = f.lower()

            if flower.endswith(imgext + vidext):
                flist.append(f)

        with zipfile.ZipFile(fd, 'a') as zf:
            [zf.write(f) for f in flist]

        return fd.getbuffer()


    def generate_html(self, files):
        self.send_header('Content-type', 'text/html')
        htmltags = []

        for f in files:
            flower = f.lower()
            url = urlparse.quote(f)
            f = escape(f)

            if flower.endswith(imgext):
                htmltags.append(f'<a href="{url}" download><img src="{url}" alt="{f}"></a>')
            elif flower.endswith(vidext):
                htmltags.append(f'<video src="{url}" controls>"{f}"</video>')

        if not htmltags:
            return bytes(html[0] + html[3] + html[1], 'utf-8')

        return bytes(html[0] + html[2] + '\n'.join(htmltags) + html[1], 'utf-8')


    def list_directory(self, path):
        files = os.listdir()
        self.send_response(200)

        # get queries to sort files and find out to use html or zip
        queries = ('s', 'r', 'zip')
        if '?' in self.path:
            num, reverse, zipf = handle_queries(self.path, queries)
            if zipf:
                wfile = self.generate_zip(files)
            elif num:
                if reverse >= 1:
                    files = sort(files, num, False)
                else:
                    files = sort(files, num)
                wfile = self.generate_html(files)
        else:
            files = sort(files)
            wfile = self.generate_html(files)

        # sends the content
        self.send_header('Content-Length', str(len(wfile)))
        self.end_headers()
        self.wfile.write(wfile)


class coolServer(http.server.ThreadingHTTPServer):
    address_family = socket.AF_INET6

    # ensure IPV6_V6ONLY is disabled
    def server_bind(self):
        self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
        return super().server_bind()


if __name__ == '__main__':

    # try to get directory from cli, otherwise try to run gui
    try:
        root = sys.argv[1]
    except IndexError:
        try:
            from PyQt5.QtWidgets import QApplication, QFileDialog, QWidget
        except ModuleNotFoundError:
            print('directory not specified and PyQt5 not installed.')
            sys.exit(1)

        class gui(QWidget):
            def __init__(self):
                super().__init__()
                global root
                root = QFileDialog.getExistingDirectory()

        app = QApplication(sys.argv)
        gui()

    try:
        os.chdir(root)
    except (NotADirectoryError, FileNotFoundError):
        print(f'"{root}" not found or is not a directory')
        sys.exit(1)

    # run server
    port = 80
    try:
        httpd = coolServer(('::', port), coolHandler)
    except PermissionError:
        port = 8080
        httpd = coolServer(('::', port), coolHandler)

    print(f'http://{find_ip()}:{port}')

    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.server_close()