#!/usr/bin/env python3
from mcstatus import JavaServer
from os.path import join as pathjoin
from bottle import route, run, static_file, template
from random import random, choice
from mcrcon import MCRcon
from os import listdir, sysconf
import minecraft_data
import configparser
import platform
import requests
import hashlib
import base64
import json
import re

# echo -n "["; ls | xargs -I{} echo -n \"{}\",; echo "]"
BG_IMAGES = listdir("./static/img/background")

@route("/")
def index():
    print(server_details())
    players = currentPlayerData()
    return template("html/index.html", **{"player_count": players["count"]["current"] , "max_players": players["count"]["max"], "news": news(), "donations": donations(), "server_details": server_details(), "map_url": CONFIG["external"]["map"], "map_download_url": CONFIG["external"]["map_download"]})

@route("/getPlayerSkins")
def getPlayerSkins():
    players = currentPlayerData()
    skins = []
    for p in players["players"]:
        hash = fetchSkin(p["uuid"])
        skins.append(hash)
    return {"skins": skins}

@route("/img/bg.png")
def random_bg_image():
    bg_file = choice(BG_IMAGES)
    print(bg_file)
    response = static_file(bg_file, root="static/img/background")
    response.set_header("Cache-Control", "no-cache")
    response.set_header("Cache-Control", "no-store")
    response.set_header("Pragma-Directive", "no-cache")
    response.set_header("Cache-Directive", "no-cache")
    response.set_header("Pragma", "no-cache")
    response.set_header("Expires", "0")
    return response

@route("/<path:path>")
def callback(path):
    return static_file(path, root="static")

def currentPlayerData():
    status = server_status()
    player = {}
    player["count"] = {}
    player["count"]["current"] = status.players.online
    player["count"]["max"] = status.players.max
    player["players"] = []
    try:
        for p in status.players.sample:
            player["players"].append({"name": p.name, "uuid": p.id})
    except TypeError:
        # If nobody is online, status.players.sample is None
        pass
    return player

def fetchSkin(uuid):
    profile = requests.get("https://sessionserver.mojang.com/session/minecraft/profile/{}".format(uuid)).json()
    properties = profile.get("properties")
    skin = list(filter(lambda x: x.get("name", "") == "textures", properties))[0]
    skin_json = json.loads(base64.b64decode(skin.get("value")))
    skin_url = skin_json.get("textures").get("SKIN").get("url")
    skin_req = requests.get(skin_url)
    player_hash = hashlib.sha256(uuid.encode()).hexdigest()
    with open('./static/img/skins/{}'.format(player_hash), 'wb') as f:
        f.write(skin_req.content)
    return player_hash

def parseConfig():
    config = configparser.ConfigParser()
    config.read("config.ini")
    return config

def donations():
    with open("donations.txt", "r") as donation_file:
        donations = donation_file.read()
    return donations

def news():
    with open("news.txt", "r") as news_file:
        news = news_file.read()
    return news

def hardware_info():
    processor = get_cpuinfo() 
    mem_bytes = sysconf('SC_PAGE_SIZE') * sysconf('SC_PHYS_PAGES')
    mem_gib = mem_bytes/(1024.**3)
    mem_pretty = round(mem_gib, 1)
    return {"cpu": {"cores": processor[0]["cpu cores"], "threads": len(processor), "name": processor[0]["model name"]}, "ram": mem_pretty}

def get_cpuinfo():
    cpus = []
    with open("/proc/cpuinfo", "r") as fp:
        cpuinfo = fp.read()
    thread_info = cpuinfo.split("\n\n")
    thread_info = list(filter(lambda x: x!="", thread_info))
    for thread in thread_info:
        thread_dict = {}
        field_lines = thread.split("\n")
        for field in field_lines:
            key, value = field.split(":")
            key = key.strip()
            value = value.strip()
            try:
                thread_dict.update({key: int(value)})
            except ValueError:
                thread_dict.update({key: value})
        cpus.append(thread_dict)
    return cpus


def paper_version():
    status = server_status()
    paper_version = status.version.name
    protocol_version = status.version.protocol
    possible_versions = list(filter(lambda x: x.get("version", 0) == protocol_version, minecraft_data.common().protocolVersions))
    try:
        mc_version = possible_versions[0].get("minecraftVersion", "Unknown")
    except:
        mc_version = "Unknown"
    return {"paper": paper_version, "minecraft": mc_version}

def datapack_info():
    try:
        with MCRcon(CONFIG["mcrcon"]["host"], CONFIG["mcrcon"]["password"], port=int(CONFIG["mcrcon"]["port"])) as mcr:
            resp = mcr.command("datapack list")

        match = re.match("There are [0-9]* data packs enabled: (\[.*\])*.*", resp)
        datapacks = []
        datapacks_string = match.group(1)
        for datapack in datapacks_string.split(","):
            match = re.match("\[(.*)\(.*\)\]", datapack.strip())
            datapack_name = match.group(1)
            datapack_name = datapack_name.strip()
            datapack_name = datapack_name.replace("file/", "")
            datapack_name = datapack_name.replace(".zip", "")
            if not datapack_name in ["vanilla", "bukkit"]:
                datapacks.append(datapack_name)
    except ConnectionRefusedError:
       datapacks = [] 

    return datapacks

def server_details():
    hardware = hardware_info()
    versions = paper_version()
    datapacks = datapack_info()
    return {"hardware": hardware, "versions": versions, "datapacks": datapacks}

def server_status():
    server = JavaServer.lookup(CONFIG["mcrcon"]["host"])
    try:
        status = server.status()
    except ConnectionRefusedError:
        status = "UNKNOWN"
    
    return status


CONFIG = parseConfig()
run(host="localhost", port=8080)