# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )

import os
import json
import random
import hashlib
import urllib.parse

from datetime import datetime

from modules import Set
from modules import markdown
from modules.Common import *

def guess_type(path):

    if "/json/" in path or ".json" in path:
        return "application/json"
    if "/css" in path or ".css" in path:
        return "text/css"
    if "/icon" in path or path.endswith(".png"):
        return "image/png"
    if path.endswith("jpg"):
        return "image/jpg"

    return "text/html"
    
def headers(server, code):

    # Basic cookie for logins to work
        
    server.send_response(code)
    server.send_header("Content-type", guess_type(server.path))

    if not server.cookie:
        server.send_header("Set-Cookie", "temp_id="+RandString())
        
    server.end_headers()

def head(title="", description="", image="", config={}):

    if image.startswith("/"): image = config.get("url","")+image

    favicon = config.get("favicon", "/icon/internet")
    
    html = """    

    <head>

    <!-- The head. Part of the HTML code where metadata about the page is storred.
    Including the metadata readable by social media websites, when generating link
    previwews, which are called mata-tags in HTML. -->

    <meta charset="utf-8">

    <title>"""+title+"""</title> <!-- Title in the browser tab -->
    <link media="all" href="/css" type="text/css" rel="stylesheet" /> <!-- CSS theme link -->
    <link rel="icon" href=\""""+favicon+""""> <!-- Tiny image in the tab -->

    <!-- Now meta tags for social media -->

    <meta property="og:site_name" content=\""""+config.get("title", "My Website")+"""">
    <meta property="og:title" content=\""""+Simplify(title, False)+"""">
    <meta property="og:description" content=\""""+Simplify(description, False)+"""">
    <meta property="og:image" content=\""""+image+"""">
    
    <!-- This meta tag is needed for pretty rendering on phones -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    </head>
    <body>

    """
    

    return html

def send(server, html, code):

    # Add headers
    headers(server, code)
    server.wfile.write(html.encode("utf-8"))
    
def tabs():

    folder = Set.Folder()+"/tabs"
    tabs = {}
    for tab in sorted(list(os.walk(folder))[0][1]):
        try:
            with open(folder+"/"+tab+"/config.json") as o:
                data = json.load(o)
                tabs[tab] = data
        except Exception as e:
            print(e)
            pass
    
    return tabs

def accounts():
    
    folder = Set.Folder()+"/accounts"
    accounts = {}

    for account in sorted(list(os.walk(folder))[0][2]):
        
        try:
            with open(folder+"/"+account) as o:
                data = json.load(o)
                data["username"] = account.replace(".json","")
                accounts[account.replace(".json","")] = data
        except Exception as e:
            print(e)
            pass
    
    return accounts

def validate(cookie):

    Accounts = accounts()
    for account in Accounts:
        if cookie in Accounts[account].get("sessions", []):
            return Accounts[account]
    return {}

def moderates(moderator, user):

    Accounts = accounts()

    if moderator not in Accounts:
        return False
    
    if user not in Accounts:
        return True

    if moderator == user:
        return True

    if rank(moderator, Accounts) < rank(user, Accounts):
        return True

def rank(account, Accounts=None):

    if not Accounts:
        Accounts = accounts()
        
    if account not in Accounts:
        return 1000000

    if not Accounts[account].get("invited_by") or Accounts[account].get("invited_by") == account:
        return 0

    return 1 + rank(Accounts[account].get("invited_by"), Accounts)
    
def articles(tab):

    folder = Set.Folder()+"/tabs/"+tab
    articles = {}
    for article in list(os.walk(folder))[0][1]:
        try:
            with open(folder+"/"+article+"/metadata.json") as o:
                data = json.load(o)
                data["tab"] = tab
                data["url"] = "/"+tab+"/"+article
                articles[article] = data
                
        except Exception as e:
            print(e)
            pass

    # Sorting articles based on timestamp
    articles = {k:articles[k] for k in sorted(articles, key=lambda y: articles[y]["timestamp"], reverse=True)}
        
    return articles

def allArticles():

    articles = {}
    f = Set.Folder()
    for tab in list(os.walk(f+"/tabs/"))[0][1]:
        folder = f+"/tabs/"+tab
    
        for article in list(os.walk(folder))[0][1]:
            try:
                with open(folder+"/"+article+"/metadata.json") as o:
                    data = json.load(o)
                    data["tab"] = tab
                    data["url"] = "/"+tab+"/"+article
                    articles[article] = data
            except Exception as e:
                print(e)
                pass

    # Sorting articles based on timestamp
    articles = {k:articles[k] for k in sorted(articles, key=lambda y: articles[y]["timestamp"], reverse=True)}
    return articles

def randomArticles():

    articles = {}
    f = Set.Folder()
    for tab in list(os.walk(f+"/tabs/"))[0][1]:
        folder = f+"/tabs/"+tab
    
        for article in list(os.walk(folder))[0][1]:
            try:
                with open(folder+"/"+article+"/metadata.json") as o:
                    data = json.load(o)
                    data["tab"] = tab
                    data["url"] = "/"+tab+"/"+article
                    articles[article] = data
            except Exception as e:
                print(e)
                pass

    # Randomizing Articles.
    newarticles = {}
    while articles:
        article = random.choice(list(articles.keys()))
        newarticles[article] = articles.pop(article)

    return newarticles

def suggestedArticles(cookie, random=False):

    if not random:
        articles = allArticles()
    else:
        articles = randomArticles()

    # Suggesting unread articles.
    newarticles = {}
    move = []
    for article in articles:
        if cookie not in articles[article].get("views", {}).get("viewers", []):
            move.append(article)
            
    for article in move:
        newarticles[article] = articles[article]

    for article in articles:
        if article not in move:
            newarticles[article] = articles[article]
        
    return newarticles
    
def previewsToSize(text):

    # Calculates roughly how many previews to fit any
    # given article.

    # A thousand character article is about 4 articles.
    return len(text)/2200


###

def MainPage(server):
    
    # Reading config
    config = Set.Load()

    # Generating <head>
    html = head(title       = config.get("title", "Website"),
                description = config.get("description", "Description"),
                config      = config
                )


    html = html + LoginButton(server)
    
    html = html + """
    
    <br>
    <br>
    <center>
    <img src=\""""+config.get("favicon", "icon/internet")+"""" alt="[LOGO]" style="height:150px;vertical-align: middle">
    <br>
    <br>

    <h2>"""+config.get("title", "My Website")+"""</h2>
    """+config.get("tagline", "")+"""
    <br>
    <br>


    """
    Tabs = tabs()
    for tab in Tabs:
        
        html = html + Button(Tabs[tab].get("title", tab),
                             "/"+tab,
                             Tabs[tab].get("icon", "folder"))

    html = html + "</center>"

    # Trending articles

    html = html + '<div class="flexity">'
    
    trends = suggestedArticles(server.cookie)
    Buffer = 20
    for n, article in enumerate(trends):

        if n >= Buffer: break
        
        article = trends[article]
        html = html + ArticlePreview(article, Tabs, server.cookie)
        
    
    html = html + '</div>'

    html = html + Footer()

    html = html + LoginButton(server)
    
    send(server, html, 200)

def ListPage(server, tab):

    Tabs = tabs()
    Articles = articles(tab)
    config = Set.Load()
    try: page = int(server.parsed.get("page", ["0"])[0])
    except Exception as e:
        print(e)
        page = 0


    Buffer = 16
    From   = Buffer*page
    To     = From+Buffer
        
    # Generating <head>
    html = head(title       = Tabs.get(tab, {}).get("title", tab),
                description = "",
                config      = config
                )

       
    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))

    # Scroll thingie
    if len(Articles) > Buffer:
        if page > 0:
            html = html + Button(str(page-1), tab+"?page="+str(page-1), "left")

        html = html + '<div class="button">'+str(page)+'</div>'

        if To < len(Articles)-1:
            html = html + Button(str(page+1), tab+"?page="+str(page+1), "right")

    html = html + """
    <br>
    <br>
    
    <!-- Article previews are neatly positioned into a grid here -->

    <div class="flexity">

    """
    
    
    rendered = 0
    for n, article in enumerate(Articles):

        if n < From: continue
        if n >= To: break
        
        html = html + ArticlePreview(Articles[article], Tabs, server.cookie)
        rendered += 1

    html = html + '</div><br>'

    # Bottom pannel for large dialogs
    if rendered >= Buffer/2:
        html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))

        # Scroll thingie
        if len(Articles) > Buffer:
            if page > 0:
                html = html + Button(str(page-1), tab+"?page="+str(page-1), "left")

            html = html + '<div class="button">'+str(page)+'</div>'

            if To < len(Articles)-1:
                html = html + Button(str(page+1), tab+"?page="+str(page+1), "right")

    html = html + LoginButton(server)
    
    send(server, html, 200)

def ArticlePage(server, url):

    user = validate(server.cookie)
    
    if url.endswith(".md"):
        url = url.replace(".md", "")
    
    config = Set.Load()
    tab, article = url.split("/")
    Tabs = tabs()
    Articles = articles(tab)
    f = Set.Folder()

    # Generating <head>
    html = head(title       = Articles.get(article, {}).get("title", article),
                description = Articles.get(article, {}).get("description", ""),
                config      = config
                )


    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
    html = html + Button(Tabs.get(tab, {}).get("title", tab), "/"+tab, Tabs.get(tab, {}).get("icon", "folder"))

    # The article itself

    html = html + '<div class="middle_section_article">'
    
    html = html + '<div class="dark_box">'
    html = html +"<center><h1>"+Articles.get(article, {}).get("title", article)+"</h1></center>"

    # Page author
    author = Articles.get(article, {}).get("author", "")
    if author:
        html = html + '<center>'+User( author )+'</center>'

    # Page views
    # Views are calculated using an iframe which only loads when the page is
    # rendered in a browser. It is also looking at whether the same cookie renders
    # the same page, to avoid double counting from the same person.
    # The iframe itself shows a graph of views.
    
    views = str(Articles.get(article, {}).get("views", {}).get("amount", 0))
    html = html + '<br><center><details><summary class="button">👁 '+views+'</summary>'
    html = html + """
    <iframe width="100%" height="500px" style="border:none; border-radius:25px;"  src="/graph"""+server.path+""""></iframe>
    <br><br>
    </details>
    """

    html = html + '</div>'
    
    # Audio recording of the article
    recording = Articles.get(article, {}).get("recording", "")
    if recording:
        html = html + '<audio controls="controls" style="min-width:100%;" src="'+recording+'"></audio>'
        

    
    html = html + '<div class="dark_box">'
    html = html + markdown.convert(f+"/tabs/"+tab+"/"+article+"/text.md")
    html = html + '</div>'

    

    # Comments

    html = html + CommentInput(server, url)

    comments = Articles.get(article, {}).get("comments", {}).get("comments", [])
    commentsTextLength = 0

    comment_edit = server.parsed.get("comment_edit", [""])[0]
    
    if comments:
        for n, comment in enumerate(comments):
            if str(n) == comment_edit and moderates(user.get("username"), comment.get("username")):
                html = html + CommentEditInput(server, comment, url, n, user)
            else:
                html = html + Comment(comment, url, n, user)

            # Needed to extend the suggestion for pages with many comments
            commentsTextLength += previewsToSize(comment.get("text", ""))

    # Requests

    requests = Articles.get(article, {}).get("comments", {}).get("requests", [])

    if requests:
        for n, comment in enumerate(requests):
            if comment.get("cookie") == server.cookie:
                html = html + Comment(comment, url, n, user, request=True)

            elif moderates(user.get("username"), comment.get("username")):
                html = html + CommentEditInput(server, comment, url, n, user, request=str(n))
            
    html = html + '</div>'
            
    # Thumbnail and suggestions

    html = html + '<div class="checklist_section_article">'

    thumbnail = Articles.get(article, {}).get("thumbnail")
    if thumbnail:

        html = html + '<div class="article_box"><br>'
        html = html + '<img style="min-width:100%; width:100%" src="'+thumbnail+'">'
        html = html + '<br><br></div>'
    
    suggestions = suggestedArticles(server.cookie, random=True)
    toomuch     = previewsToSize(open(f+"/tabs/"+tab+"/"+article+"/text.md").read())
    toomuch    += commentsTextLength
    
    for n, title in enumerate(suggestions):

        if server.path in suggestions[title].get("url") :
            continue
        
        if n > toomuch:
            break
        
        article = suggestions[title]
        html = html + ArticlePreview(article, Tabs, server.cookie)
        html = html + ""
    

    html = html + LoginButton(server)
    
    send(server, html, 200)

def AccountPage(server, account):

    config   = Set.Load()
    Accounts = accounts()
    Tabs     = tabs()
    Articles = allArticles()
    f = Set.Folder()

    # Generating <head>
    html = head(title       = Safe(Accounts.get(account, {}).get("title", account)),
                description = Safe(Accounts.get(account, {}).get("bio"  , "")),
                config      = config
                )

    
    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))

    # Name and bio

    html = html + '<div class="middle_section_article">'
    
    html = html + '<div class="dark_box">'
    html = html +"<center><h1>"+Accounts.get(account, {}).get("title", account)+"</h1>"

    # Rank

    Rank = rank(account)
    html = html + Button("Rank "+str(Rank), "", icon="analytics")


    html = html + '</center>'
    
    # Website
    
    website = Safe(Accounts.get(account, {}).get("website"  , ""))
    if website:

        webtitle = website.replace("https://", "").replace("http://", "")
        if not website.startswith("http"): website = "http://"+website
        
        html = html + '<center>'
        html = html + '<img style="vertical-align: middle" src="/icon/internet">'
        html = html + '<a href="'+website+'"> '+webtitle+'</a>'
        html = html + '</center>'

    # Email
        
    email = Safe(Accounts.get(account, {}).get("email"  , ""))
    if email:

        html = html + '<center>'
        html = html + '<img style="vertical-align: middle" src="/icon/frase">'
        html = html + '<a href="mailto:'+email+'"> '+email+'</a>'
        html = html + '</center>'

    # Mastodon

    mastodon = Safe(Accounts.get(account, {}).get("mastodon"  , ""))
    if mastodon:

        # It could be mastodon url and not handle.
        try:
            if "/" in mastodon:
                mastodon = mastodon.replace("https://", "").replace("http://", "")
                mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0]

            mastolink = "https://"+mastodon[1:].split("@")[1]+"/@"+mastodon[1:].split("@")[0]

            html = html + '<center>'
            html = html + '<img style="vertical-align: middle" src="/icon/mastodon">'
            html = html + '<a href="'+mastolink+'"> '+mastodon+'</a>'
            html = html + '</center>'
        except:
            pass

    # Matrix

    matrix = Safe(Accounts.get(account, {}).get("matrix"  , ""))
    if matrix:

        # Matrix could be the matrix.to link
        if "/" in matrix:
            matrix = matrix[matrix.rfind("/")+1:]
        
        matrixlink = "https://matrix.to/#/"+matrix
        
        html = html + '<center>'
        html = html + '<img style="vertical-align: middle" src="/icon/element">'
        html = html + '<a href="'+matrixlink+'"> '+matrix+'</a>'
        html = html + '</center>'

    if any((website, email, mastodon, matrix)):
        html = html + '<br>'
        
    html = html + '</div>'

    invited_by = Accounts.get(account, {}).get("invited_by", "")
    if invited_by:
        html = html + '<div class="dark_box">'
        html = html +"<center>Invited by:<br>"+User(invited_by)+"</center>"
        html = html + '</div>'
        
    bio  = Safe(Accounts.get(account, {}).get("bio"  , ""))
    if bio:
        html = html + '<div class="dark_box">'
        html = html + markdown.convert(bio, False)+'<br>'
        html = html + '</div>'


    # Posts by this account

    html = html + '<div class="flexity">'

    for article in Articles:
        
        if Articles[article].get("author") != account:
            continue
        
        html = html + ArticlePreview(Articles[article], Tabs, server.cookie)

    html = html + '</div>'

    html = html + '</div>'

    # Thumbnail and suggestions

    html = html + '<div class="checklist_section_article">'

    avatar = Safe(Accounts.get(account, {}).get("avatar"  , ""))
    if avatar:
        html = html + '<div class="article_box"><br>'
        html = html + '<img style="min-width:100%; width:100%" src="'+avatar+'">'
        html = html + '<br><br></div>'
        
    # Invited

    invited = Accounts.get(account, {}).get("invited", [])
    if invited:
        html = html + '<center><div class="button">Invited:</div></center>'
        for username in invited:
            if username in Accounts:
                html = html + '<div class="dark_box">'
                html = html + '<center>' + User(username) + '</center>\n'
                html = html + '</div>'


    html = html + LoginButton(server)
            
    send(server, html, 200)

def LoginPage(server):

    config   = Set.Load()
    Accounts = accounts()
    f = Set.Folder()

    wrongname = server.parsed.get("wrong", [""])[0]

    # Generating <head>
    html = head(title       = "Login",
                description = "Login",
                config      = config
                )

    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))

    html = html + '<center>'

    if wrongname:
        html = html + '\n<br>Wrong Username / Password<br>\n'
    
    html = html + """
    
    <div class="middle_section_article">

    <div class="dark_box">

    <form action="do_login">
    
    <img style="vertical-align: middle" src="/icon/user">
    <input  class="button" style="width:90%" maxlength="500" id="user_name" required="" name="user_name" pattern="[A-Za-z0-9\.\-\_\]*" placeholder="Username..."></input>
    <br>
    <img style="vertical-align: middle" src="/icon/lock">
    <input  class="button" style="width:90%" maxlength="500" id="password" type="password" required="" name="password" placeholder="Password..."></input><br>

    <button class="button" type="submit">    
    <img style="vertical-align: middle" src="/icon/unlock">
    Login
    </button>

    </form>

    </div></div>
    
    <div class="checklist_section_article">
    
    <div class="dark_box">

    Don't have an account? <br>
    """
    html = html + Button("Register", "/register", icon="user_new")

    
    send(server, html, 200)

def RegisterPage(server):

    user = validate(server.cookie)
    
    config   = Set.Load()
    Accounts = accounts()
    f = Set.Folder()

    code = server.parsed.get("code", [""])[0]
    userexists = server.parsed.get("userexists", [""])[0]
    wrongcode  = server.parsed.get("wrongcode", [""])[0]

    # Generating <head>
    html = head(title       = "Register",
                description = "Register",
                config      = config
                )

    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))

    html = html + '<center>'

    
    html = html + """
    
    <div class="middle_section_article">

    <div class="dark_box">

    <form action="do_register">
    """

    if wrongcode:
        html = html + "Invalid Invite Code.<br><br>"
    
    if not code and not user:
        html = html + """
        
        <img style="vertical-align: middle" src="/icon/user_link">
        <input  class="button" style="width:90%" maxlength="500" id="code" required="" name="code" placeholder="Invite code..."></input>
    
        """
        username = ""
        
    else:
        for account in Accounts:
            if code in Accounts[account].get("invite_codes", []):
                username = Simplify(Accounts[account]["invite_codes"][code], "file")

                html = html + '<center>Invited by:<br><br>'
                html = html + User(account)+'<br>'
                html = html + '<input type="hidden" name="code" value="'+code+'">'
                
                break


    if not user:

        
        if userexists:
            html = html + "Username is taken.<br><br>"
        
        html = html + """<img style="vertical-align: middle" src="/icon/user">
        <input  class="button" style="width:90%" maxlength="500" id="user_name" required="" name="user_name" pattern="[A-Za-z0-9\.\-\_\]*" placeholder="Username..." value=\""""+username+""""></input>
        <br>
        <img style="vertical-align: middle" src="/icon/lock">
        <input  class="button" style="width:90%" maxlength="500" id="password" type="password" required="" name="password" placeholder="Password..."></input><br>

        <button class="button" type="submit">    
        <img style="vertical-align: middle" src="/icon/user_new">
        Register
        </button>
        """

    elif code:
        html = html + "Share this page with those you invited."

    else:
        html = html + "<br>You already registered and logged in."
        
    html = html + """
    </form>

    </div></div>
    
    <div class="checklist_section_article">
    
    <div class="dark_box">

    Have an account? <br>
    """
    html = html + Button("Login", "/login", icon="unlock")

    
    send(server, html, 200)
    
def SettingsPage(server):

    user = validate(server.cookie)

    # Authorization check
    if not user:
        AccessDenied(server)
        return
    
    config = Set.Load()

    # Generating <head>
    html = head(title       = "Settings",
                description = "Settings",
                config      = config
                )

    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
    html = html + Button("Public Profile", "/account/"+user.get("username", ""), image="/pictures/monkey.png")
    
    
    # Main settings

    html = html + """
    
    <div class="middle_section_article">

    <div class="dark_box">
    
    <center><h2>Public Info</h2></center>
    
    <form action="update_account">
    
    <img style="vertical-align: middle" src="/icon/user">
    <input class="button" style="width:90%" maxlength="200" name="title" placeholder="Visible Name" value='"""+user.get("title", "")+"""'>
    
    <img style="height:40px; vertical-align: middle" src="/pictures/monkey.png">
    <input class="button" style="width:90%" maxlength="200" name="avatar" placeholder="Link To Profile Picture" value='"""+user.get("avatar", "")+"""'>
        
    <br>
    <center>
    <img style="vertical-align: middle" src="/icon/scene">
    <b>Bio</b><br>
    <textarea class="toot" rows="10" style="width:95%" maxlength="10000" name="bio">"""+Safe(user.get("bio", ""))+"""</textarea>
    </center>


    <img style="vertical-align: middle" src="/icon/internet">
    <input class="button" style="width:90%" maxlength="200" name="website" placeholder="Personal Website" value='"""+user.get("website", "")+"""'>
        
    <img style="vertical-align: middle" src="/icon/frase">
    <input class="button" style="width:90%" maxlength="200" name="email" placeholder="Publicly Visible Email" value='"""+user.get("email", "")+"""'>

    <img style="vertical-align: middle" src="/icon/mastodon">
    <input class="button" style="width:90%" maxlength="200" name="mastodon" placeholder="Mastodon Handle" value='"""+user.get("mastodon", "")+"""'>

    <img style="vertical-align: middle" src="/icon/element">
    <input class="button" style="width:90%" maxlength="200" name="matrix" placeholder="Matrix Handle" value='"""+user.get("matrix", "")+"""'>
    
    <button class="button" type="submit">    
    <img style="vertical-align: middle" src="/icon/ok">
    Save
    </button>

    </form>
    </div>

    """

    # Current Logged in Sessions

    sessions = user.get("sessions", {})

    html = html + '<div class="dark_box" id="sessions">'
    html = html + '<center><h2>Active Sessions</h2>'
    
    for cookie in sessions:

        session = sessions[cookie]

        CancelButton = Button("Log Out", "/log_out?cookie="+cookie, icon="cancel")
        if server.cookie == cookie:
            html = html + '<br><img title="This Session" style="vertical-align: middle" src="/icon/checked">'
        else:
            html = html + '<br><img title="Other Browser" style="vertical-align: middle" src="/icon/unchecked">'
        html = html + '<input class="button" style="width:50%" value="' + session + '">'+CancelButton

    html = html + '<br><br>'
    html = html + '</center>'
    html = html + '</div>'

    # Invites and Invite codes

    invite_codes = user.get("invite_codes", {})

    html = html + '<div class="dark_box" id="invites">'
    html = html + '<center><h2>Invites</h2></center>'

    for code in invite_codes:

        nick = invite_codes[code]

        Open = ""
        if code == server.parsed.get("code", [""])[0]:
            Open = "open"
        
        html = html + '<details '+Open+' class="dark_box"><summary id="invite_'+code+'" class="button">'
        html = html + '<img style="vertical-align: middle" src="/icon/user">'+nick
        html = html + '</summary><br>'
        html = html + '<img style="vertical-align: middle" src="/icon/user_link">'
        html = html + '<input class="button" style="width:90%" value="' + code + '">'
        html = html + Button("Share Link", "/register?code="+code, icon="link")
        html = html + Button("Cancel", "/cancel_invite?code="+code, icon="cancel")
        html = html + '</details>'

    html = html + """
    
    <form action="/create_invite">
    
    <input name="nick" class="button" required="" style="width:50%" placeholder="Name of the person you invite">

    <button class="button" type="submit">    
    <img style="vertical-align: middle" src="/icon/user_new">
    Invite
    </button>

    </form>
    
    </div>
    </div>

    """

    notifications = user.get("notifications","")

    if notifications:

        html = html + '<div class="checklist_section_article">'

        for notification in notifications:

            html = html + '<div class="article_box">'
            html = html + '<br><a href="/read_notification?code='+notification.get("code", "")+'">'
            html = html + notification.get("text", "Notification")
            html = html + '</a><br><br>'
            html = html + '</div>'
            
    send(server, html, 200)

    
###

def Button(text, link, icon="", image=""):

    html = """
    <a class="button" href=\""""+link+"""">"""
    if icon:
        html = html + """
        <img src="/icon/"""+icon+"""" style="vertical-align: middle">"""
    if image:
        html = html + """
        <img src=\""""+image+"""" style="height:40px;vertical-align: middle">"""
        
    html = html + """
    """+text+"""</a>

    """
    return html
    
def ArticlePreview(article, Tabs, cookie=""):
    
    html =  """
        
    <div class="article_box">
    
    """
    url, tab = article.get("url", ""), article.get("tab","")

    sup = ""
    if cookie not in article.get("views", {}).get("viewers", ""):
        sup = '<center><sup><b> &nbsp; Unread &nbsp; </b></sup></center><br>'
    
    html = html + '<a href="'+url+'"><h1>'
    html = html + '<img src="/icon/'+Tabs.get(tab, {}).get("icon", "folder")+'" style="vertical-align: middle">'
    html = html + article.get("title", "")+"</h1></a>"+sup+"\n"

    if "thumbnail" in article:
        html = html + '<center><a href="'+url+'"><img src="'+article["thumbnail"]+'"></a></center>'

    author = article.get("author", "")
    if author:
        html = html + '<br><center>'+User( author )+'</center>'

    views = str(article.get("views", {}).get("amount", 0))
    try:    comments = str(len(article.get("comments", {}).get("comments")))
    except: comments = "0"
    html = html +'<br><center> 👁 '+views+' 💬 '+comments+' </center>'
        
    html = html + "<br>"+markdown.convert(article.get("description", ""), False)+"<br><br>"
    html = html + '</div>\n'

    return html

def Footer():

    html = """
    
    <center>

    """
    html = html + Button("Powered with BDServer", "https://codeberg.org/blenderdumbass/BDServer/", "codeberg")


    html = html + """
    
    </center>

    """

    return html

def User(username, stretch=False):

    try:
        with open(Set.Folder()+"/accounts/"+username+".json") as o:
            account = json.load(o)
    except:
        account = {}

    # We are doing a lot of reductions in case somebody sneaks html code.
    avatar = Safe(account.get("avatar", ""))
    if not avatar: avatar = "/pictures/monkey.png"
    username = Safe(username)
    title = Safe(account.get("title", username))
    if account:
        html = '<img style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;<a href="/account/'+username+'">'+title+'</a></center>\n'
    else:
        html = '<img style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;'+title+'</center>\n'

    return html

def Graph(server, url):

    user = validate(server.cookie)

    html = """
    <head>
    <link media="all" href="/css" type="text/css" rel="stylesheet" /> <!-- CSS theme link -->
    </head>
    
    <style>
    html {
    background-color: none; 
    background-image: none;
    }
    </style>

    """
    
    if url.endswith(".md"):
        url = url.replace(".md", "")
        
    try:
        with open(Set.Folder()+"/tabs"+url+"/metadata.json") as o:
            article = json.load(o)
    except:
        article = {}

    dates = article.get("views", {}).get("dates", {})
    largest = max(dates.values())

    dateformat = "%Y-%m-%d"
    startdate = datetime.strptime(sorted(list(dates.keys()), reverse=True)[0], dateformat)
    enddate   = datetime.strptime(sorted(list(dates.keys()), reverse=True)[-1], dateformat)
    
    alldays = int((startdate - enddate).days)
    
    for n, date in enumerate(sorted(dates, reverse=True)):

        amount = dates[date]
        width = 100 / (alldays+1)
        height = 60 * (amount / largest)

        cd = datetime.strptime(date, dateformat)
        nd = int((startdate - cd).days)
        
        html = html + '<div style="top:'+str(60-height)+'%;right:'+str((nd)*(width))+'%; width:'+str(max(width-0.5,0.5))+'%; height:'+str(height)+'%" title="'+str(date)+' - '+str(amount)+' views" class="graph_line"></div>\n'
    

    # Saving the view
    cookies = [server.cookie]

    if user:

        # If user is logged in, we want to record
        # per reading the article, on all pers
        # sessions.
        
        for cookie in user.get("sessions"):
            if cookie not in cookies:
                cookies.append(cookie)
    
    for cookie in cookies:
        if cookie not in article.get("views", {}).get("viewers", []):
          
            article["views"]["amount"] += 1
            article["views"]["viewers"].append(cookie)

    with open(Set.Folder()+"/tabs"+url+"/metadata.json", "w") as save:
        json.dump(article, save, indent=4)
        
    send(server, html, 200) 

def CommentInput(server, url):

    user = validate(server.cookie)
    
    html = """
    <div class="dark_box" id="comments">
    
    <br><center>
    
    <form action="/comment">
    <input type="hidden" id="url" name="url" value=\""""+url+"""">

    """

    if not user:
        html = html + '<img style="vertical-align: middle" src="/icon/user">'
        html = html + '<input class="button" style="width:90%" maxlength="200" name="username" placeholder="Optional Nick Name">'

    else:
        html = html + User(user.get("username", ""))+'<center>'
    
    html = html + """
    
    <br>

    <img style="vertical-align: middle" src="/icon/bug">
    <input class="button" maxlength="200" style="width:90%" name="warning" placeholder="Optional Trigger Warning">
    
    <textarea class="toot" rows="10" required="" style="width:95%" maxlength="10000" name="text">In my opinion </textarea>
    
    <br>

    <button class="button" type="submit">    
    <img style="vertical-align: middle" src="/icon/frase">
    Post
    </button>
    

    </form>
    
    </center>

    </div>
    """
    return html

def CommentEditInput(server, comment, url, n=0, user=None, request=None):

    Accounts = accounts()
    
    html = '<div class="dark_box" id="comment_'+str(n)+'">'
    
    html = html + """
    
    <form action="/comment">
    <input type="hidden" id="url" name="url" value=\""""+url+"""">
    <input type="hidden" id="number" name="number" value=\""""+str(n)+"""">
    """
    if request:
        html = html + '<input type="hidden" id="request" name="request" value=\"'+request+'">'
        html = html + '<br><center><sup><b> &nbsp; Pending Approval &nbsp; </b></sup></center><br>' 

    account = comment.get("username", "")
    if account in accounts():
        html = html + User(Safe(account))
        
    elif account:
        html = html + '<img style="vertical-align: middle" src="/icon/user">'
        html = html + '<input class="button" style="width:90%" maxlength="200" name="username" placeholder="Optional Nick Name" value="'+account+'">'

    warning = comment.get("warning", "")
    html = html + """
    
    <br>
    <img style="vertical-align: middle" src="/icon/bug">
    <input class="button" maxlength="200" style="width:90%" name="warning" placeholder="Optional Trigger Warning" value=\""""+warning+"""">

    """


    html = html + """<textarea class="toot" rows="20" required="" style="width:95%" maxlength="10000" name="text">"""
    
    html = html + Safe(comment.get("text", ""))
    html = html + '</textarea>'

    html = html + """
    
    <button class="button" type="submit">    
    <img style="vertical-align: middle" src="/icon/ok">
    Save
    </button>

    """

    html = html + '</form>'

    html = html + '</div>'

    return html
    
def Comment(comment, url, n=0, user=None, request=False):

    if not request:
        html = '<div class="dark_box" id="comment_'+str(n)+'">'
    else:
        html = '<div class="dark_box" id="request_'+str(n)+'">'

    account = comment.get("username", "Anonymous User")
    html = html + User(account) + '<br>\n'

    if request:
        html = html + '<center><sup><b> &nbsp; Pending Approval &nbsp; </b></sup></center><br>' 

    warning = comment.get("warning", "")
    if warning:
        html = html + '<details>'
        html = html + '<summary class="button" title="Click to show anyway.">'
        html = html + '<img src="/icon/bug" style="vertical-align: middle">'
        html = html + warning
        html = html + '</summary>'
    
    html = html + markdown.convert(Safe(comment.get("text", "")), False)+'<br>'

    if warning:
        html = html + '</details>'
    
    if moderates(user.get("username"), account):
        html = html + '<a class="button" href="/'+url+'?comment_edit='+str(n)+'#comment_'+str(n)+'">'
        html = html + '<img src="/icon/edit" style="vertical-align: middle"> Edit'
        html = html + '</a>'

        html = html + '<a class="button" href="/delete_comment?url='+urllib.parse.quote_plus(url)+'&number='+str(n)+'">'
        html = html + '<img src="/icon/cancel" style="vertical-align: middle"> Delete'
        html = html + '</a>'
        
    
    html = html + '</div>\n'

    

    return html

def LoginButton(server):

    user = validate(server.cookie)
    
    html = '<div class="login_fixed">'

    if not user:
        html = html + '<a class="button" href="/login">'
        html = html + '<img style="vertical-align: middle" src="/icon/unlock"> Login'
        html = html + '</a>'

    else:

        notifications = len(user.get("notifications", []))
        sup = ""
        if notifications:
            sup = '<sup>'+str(notifications)+'</sup>'
        
        html = html + '<a class="button" href="/settings">'
        avatar = Safe(user.get("avatar", ""))
        if not avatar: avatar = "/pictures/monkey.png"
        html = html + '<img style="height:40px;vertical-align: middle" src="'+avatar+'"> '
        html = html + user.get("title", user.get("username", "Anonymous"))
        html = html + sup
        html = html + '</a>'

    html = html + '</div>'

    return html


def NotFound(server):

    config = Set.Load()
    
    html = head(title       = "404 Not Found",
                description = "404 Not Found",
                config      = config
                )

    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
    
    html = html + """
    
    <center>
    <div class="article_box">
    <h1>404 Not Found</h1>
    </center>

    """

    send(server, html, 404)
    
def AccessDenied(server):

    config = Set.Load()
    
    html = head(title       = "403 Access Denied",
                description = "403 Access Denied",
                config      = config
                )

    html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
    
    html = html + """
    
    <center>
    <div class="article_box">
    <h1>403 Access Denied</h1>
    </center>

    """

    send(server, html, 404)
    

###

def Redirect(server, url):

    html = """<meta http-equiv="Refresh" content="0; url='"""+url+"""'" />"""
    send(server, html, 200)

def Login(server):

    username = server.parsed.get("user_name", [""])[0]
    password = server.parsed.get("password" , [""])[0]
    hashed   = hashlib.sha512(password.encode("utf-8")).hexdigest()
    
    Accounts = accounts()

    # Failed authentication
    if username not in Accounts or hashed != Accounts[username].get("password"):
        Redirect(server, "/login?wrong=username")

    # Succesfull authentication
    else:

        account = Accounts[username]
        
        if "sessions" not in account:
            account["sessions"] = {}

        account["sessions"][server.cookie] = server.headers.get("User-Agent")
        f      = Set.Folder()
        folder = f+"/accounts"
        with open(folder+"/"+username+".json", "w") as save:
            json.dump(account, save, indent=4)

        # Move the cookie arround
        # When a login happens, we want to make the server know
        # which articles the person already read and stuff. So
        # we want to come all the cookies. Arround.

        articles = allArticles()

        for title in articles:
            article = articles[title]

            for cookie in account["sessions"]:
                if cookie != server.cookie:

                    # Making it so it knows what you were watching previously.
                    if cookie in article.get("views", {}).get("viewers", [])\
                    and server.cookie not in article["views"]["viewers"]:
                        
                        article["views"]["viewers"].append(server.cookie)

                    # Making it so previously logged in account would know.
                    # what you were watching thus far from this cookie.
                    if server.cookie in article.get("views", {}).get("viewers", [])\
                    and cookie not in article["views"]["viewers"]:

                        article["views"]["viewers"].append(cookie)
                    
                        
            with open(f+"/tabs"+article.get("url")+"/metadata.json", "w") as save:
                json.dump(article, save, indent=4)
            
        
            
        Redirect(server, "/")

def Register(server):

    # If by mistake we are logged in
    user = validate(server.cookie)
    if user:
        Redirect(server, "/register")

    username = Simplify(server.parsed.get("user_name", [""])[0], "file")
    code     = server.parsed.get("code", [""])[0]
    password = server.parsed.get("password" , [""])[0]
    hashed   = hashlib.sha512(password.encode("utf-8")).hexdigest()
    
    Accounts = accounts()

    # We avoid username swappage
    if username in Accounts or not username:
        if code:
            Redirect(server, "/register?code="+code+"&userexists=True#user_name")
        else:
            Redirect(server, "/register?userexists=True#user_name")
        return

    # Validating the invite code

    invited_by = ""
    for account in Accounts:
        if code in Accounts[account].get("invite_codes", []):
            invited_by = account
            break

    if not invited_by:
        Redirect(server, "/register?wrongcode=True")
        return

    # Now we can finally make our account.

    # New account first
    account = {
        "username":username,
        "bio":"",
        "invite_codes":{},
        "invited":[],
        "invited_by":invited_by,
        "password":hashed,
        "title":username,
        "email":"",
        "website":"",
        "mastodon":"",
        "matrix":"",
        "sessions":{
            server.cookie:server.headers.get("User-Agent")
        }
    }

    f      = Set.Folder()
    folder = f+"/accounts"
    
    with open(folder+"/"+username+".json", "w") as save:
        json.dump(account, save, indent=4)

    # Now the invitor changes

    account = Accounts[invited_by]
    del account["invite_codes"][code]
    account["invited"].append(username)

    with open(folder+"/"+account.get("username", "")+".json", "w") as save:
        json.dump(account, save, indent=4)

    Redirect(server, "/settings")

    # Notification
    Notify(invited_by, "/account/"+Safe(username), "@"+Safe(username)+" has registered from your invitation.")
    
def LogOut(server):

    user =  validate(server.cookie)

    # Authorization check
    if not user:
        AccessDenied(server)
        return
    
    cookie = server.parsed.get("cookie", [""])[0]

    # This might be an attack. So we don't want that.
    if cookie not in user.get("sessions",{}):
        Redirect(server, "/")
        return

    del user["sessions"][cookie]

    f      = Set.Folder()
    folder = f+"/accounts"
    with open(folder+"/"+user.get("username", "")+".json", "w") as save:
        json.dump(user, save, indent=4)

    # If the user logged out this session
    if cookie == server.cookie:
        Redirect(server, "/")

    else:
        Redirect(server, "/settings#sessions")
    
    
def UpdateAccount(server):

    user = validate(server.cookie)
    
    # Authorization check
    if not user:
        AccessDenied(server)
        return
    
    keys = [
        "title",
        "avatar",
        "bio",
        "website",
        "email",
        "mastodon",
        "matrix"
    ]

    for key in keys:
        data = server.parsed.get(key, [""])[0]
        user[key] = Safe(data)

    f      = Set.Folder()
    folder = f+"/accounts"
    with open(folder+"/"+user.get("username", "")+".json", "w") as save:
        json.dump(user, save, indent=4)

    Redirect(server, "/settings")
        
def DoComment(server):

    user = validate(server.cookie)
    Accounts = accounts()
        
    url  = server.parsed.get("url", ["/"])[0]
    if not url.startswith("/"): url = "/" + url
    
    text    = server.parsed.get("text", [""])[0]
    nick    = server.parsed.get("username", [""])[0]
    warn    = server.parsed.get("warning", [""])[0]
    number  = server.parsed.get("number", [""])[0]
    request = server.parsed.get("request", [""])[0]

    wasnumber = number
    
    metadata = Set.Folder()+"/tabs"+url+"/metadata.json"
    
    try:
        with open(metadata) as o:
            article = json.load(o)
    except:
        Redirect(server, "/")
        return

    if "comments" not in article:
        article["comments"] = {}
    if "comments" not in article["comments"]:
        article["comments"]["comments"] = []
    if "requests" not in article["comments"]:
        article["comments"]["requests"] = []

    comment = {
        "text":text
    }

    placeRedirect = "#comment_"
    
    if warn:
        comment["warning"] = warn

    
        
    if not nick and user:
        comment["username"] = user.get("username", "")
        place = "comments"

    elif request:

        if  nick in Accounts or not nick:
            nick = "Anonymous Guest"
        comment["username"] = nick        
        del article["comments"]["requests"][int(request)]
        place = "comments"
        number = ""
        
    else:

        if  nick in Accounts or not nick:
            nick = "Anonymous Guest"
        comment["username"] = nick
        placeRedirect = "#request_"
        place = "requests"


    if not user:
        comment["cookie"] = server.cookie
    
        
    if not number:
        article["comments"][place].append(comment)
        number = len(article["comments"][place])-1
    else:
        number = int(number)
        if moderates(user.get("username"), article["comments"]["comments"][number]["username"]):

            # Making sure moderators done get credit for small edits
            # in comments.
            
            originalcommet = article["comments"]["comments"][number]
            if originalcommet.get("username") in Accounts:
                comment["username"] = originalcommet["username"]
                
            article["comments"]["comments"][number] = comment
        

    try:
        with open(metadata, "w") as save:
            json.dump(article, save, indent=4)
    except:
        pass

    if not number:
        placeRedirect = "#comments"
        number = ""

    Redirect(server, url+placeRedirect+str(number))

    if not wasnumber:
    
       # Notification
       username = user.get("username", nick)
       if username != article.get("author"):
           Notify(article.get("author"), url+placeRedirect+str(number), "@"+Safe(username)+" commented: <br><br> <b>"+article.get("title", "")+"</b><br><br><i>"+Safe(text[:200])+"</i>")

       # Mention notifications
       searchText = text.lower()
       for account in Accounts:

           # The author already got the notification.
           if account == article.get("author"):
               continue

           name = Accounts[account].get("title", account)
           if account.lower() in searchText or name.lower() in searchText:

               Notify(account,
                      url+placeRedirect+str(number),
                      "@"+Safe(username)+" mentioned you: <br><br> <b>"+article.get("title", "")+"</b><br><br><i>"+Safe(text[:200])+"</i>")


def DeleteComment(server):

    user = validate(server.cookie)
    
    # Authorization check
    if not user:
        AccessDenied(server)
        return
    
    url  = server.parsed.get("url", ["/"])[0]
    if not url.startswith("/"): url = "/" + url
    
    number  = int(server.parsed.get("number", ["0"])[0])

    metadata = Set.Folder()+"/tabs"+url+"/metadata.json"
    
    try:
        with open(metadata) as o:
            article = json.load(o)
    except:
        Redirect(server, "/")
        return

    comment = article["comments"]["comments"][number]
    
    if moderates(user.get("username", ""), comment.get("username", "")):

        del article["comments"]["comments"][number]

        
        try:
            with open(metadata, "w") as save:
                json.dump(article, save, indent=4)
        except:
            pass

    if number:
        redirect = "#comment_"+str(number-1)
    else:
        redirect = "#comments"
        
    Redirect(server, url+redirect)

def CancelInvite(server):

    user = validate(server.cookie)

    # Authorization check
    if not user:
        AccessDenied(server)
        return
    
    code = server.parsed.get("code", [""])[0]
    if user:
        del user["invite_codes"][code]
        f      = Set.Folder()
        folder = f+"/accounts"
        with open(folder+"/"+user.get("username", "")+".json", "w") as save:
            json.dump(user, save, indent=4)
        Redirect(server, "/settings#invites")
            
    else:
        Redirect(server, "/")
    
def CreateInvite(server):

    user = validate(server.cookie)

    # Authorization check
    if not user:
        AccessDenied(server)
        return
    
    nick = server.parsed.get("nick", [""])[0]
    if not nick: nick = "Unknown"
    code = RandString()
    if user:
        user["invite_codes"][code] = nick
        
        f      = Set.Folder()
        folder = f+"/accounts"
        with open(folder+"/"+user.get("username", "")+".json", "w") as save:
            json.dump(user, save, indent=4)
        Redirect(server, "/settings?code="+code+"#invite_"+code)
            
    else:
        Redirect(server, "/")


def Notify(username, link, text):

    Accounts = accounts()

    try:
        account = Accounts[username]
        
        if "notifications" not in account:
            account["notifications"] = []

        notification = {
            "link":link,
            "text":text,
            "code":RandString(20)
        }

        account["notifications"].append(notification)
        f      = Set.Folder()
        folder = f+"/accounts"
        with open(folder+"/"+account.get("username", "")+".json", "w") as save:
            json.dump(account, save, indent=4)
            
    except Exception as e:
        print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Unable to set notification!", e, link, text)
    
def ReadNotification(server):

    user = validate(server.cookie)

    # Authorization check
    if not user:
        AccessDenied(server)
        return

    code = server.parsed.get("code", [""])[0]

    try:

        # Apparently I'm stupid to use a link here.
        # But I already commited to it and I'm editing
        # on a live server. So here we go... O.o
        
        for n, notification in enumerate(user.get("notifications")):
            if notification.get("code") == code:
                break

        n = user["notifications"].pop(n)

        f      = Set.Folder()
        folder = f+"/accounts"
        with open(folder+"/"+user.get("username", "")+".json", "w") as save:
            json.dump(user, save, indent=4)

        Redirect(server, n.get("link", "/"))

    except Exception as e:
        print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Unable to read notification!", e)