#!/usr/bin/python
###
### Build script for the la-warch app. This script is responsible
### for building app from source, and config. Config file is 
### 
### ./config.json
###
### This file has documentation, with options that could be change to
### make this app more flexible for the end users.

if not __name__ == "__main__":
    print("This is build system. Can be only executed as app.")
    exit(-2)

import json
import pathlib
import shutil
import subprocess
import datetime

# Current release
release = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")

# Root project directory
root = pathlib.Path("./")

# This is root source dir
source = root / pathlib.Path("./source/")

# Sources with types of content
script_source = source / pathlib.Path("./scripts/")
sass_source = source / pathlib.Path("./theme/")
static_files = source / pathlib.Path("./static/")
templates = source / pathlib.Path("./templates/")

# Specific files, like loaders, main view, etc.
app_view_file = templates / pathlib.Path("view.html")
script_loader_file = script_source / pathlib.Path("loader.js")
sass_loader_file = sass_source / pathlib.Path("loader.sass")
sass_loading_screen_file = sass_source / pathlib.Path("loading-screen.sass")
config = root / pathlib.Path("config.json")

# Build dir
build = root / pathlib.Path("./build/")

# Result specific files
app_view_result_file = build / pathlib.Path("index.html")
script_loader_result_file = build / pathlib.Path("bundle.js")
sass_loader_result_file = build / pathlib.Path("bundle.css")
sass_loading_screen_tmp_file = build / pathlib.Path("loading-screen.css")
static_files_output = build / pathlib.Path("static")
output_config = static_files_output / pathlib.Path("config")

# App config directory
app_config = root / pathlib.Path("config/")
app_example_config = root / pathlib.Path("example_config/")

# Check app config
if not app_config.is_dir():
    app_config = app_example_config
    print("Custom config not found. Using example config.")

if not app_config.is_dir():
    print("App example config not exists.")
    exit(-1)

# App config files and directories
enviroment = app_config / pathlib.Path("enviroment.json")
rooms = app_config / pathlib.Path("rooms.json")
items = app_config / pathlib.Path("items.json")
textures = app_config / pathlib.Path("textures")
objects = app_config / pathlib.Path("objects")

# Checking config directories
if not enviroment.is_file():
    print("Enviroment file not exists.")
    exit(-1)

if not rooms.is_file():
    print("Rooms index config not exists.")
    exit(-1)

if not items.is_file():
    print("Items index config not exists.")
    exit(-1)

if not textures.is_dir():
    print("Textures directory in config not exists.")
    exit(-1)

if not objects.is_dir():
    print("Objects directory in config not exists.")
    exit(-1)

# Checking that directories exists
if not source.is_dir():
    print("Source directory not exists.")
    exit(-1)

if not script_source.is_dir():
    print("Script source directory not exists.")
    exit(-1)

if not sass_source.is_dir():
    print("Sass source director not exists.")
    exit(-1)

if not static_files.is_dir():
    print("Static files directory not exists.")
    exit(-1)

if not templates.is_dir():
    print("Templates directory not exists.")
    exit(-1)

# Checking files
if not app_view_file.is_file():
    print("App view file not exists.")
    exit(-1)

# Check config file
if not config.is_file():
    print("App config file not exists.")
    exit(-1)

# Check script loader file
if not script_loader_file.is_file():
    print("Script loader file not exists.")
    exit(-1)

# Check SASS loader file
if not sass_loader_file.is_file():
    print("SASS loader file not exists.")
    exit(-1)

# Check that SASS loading screen file exists
if not sass_loading_screen_file.is_file():
    print("SASS loading screen file not exists.")
    exit(-1)

# Prepare result directory
if build.is_dir():
    shutil.rmtree(build)

build.mkdir()

# Parse config json
try:
    with config.open() as handle:
        config = json.loads(handle.read())
except event:
    print(str(event))
    print("Can not parse config file.")
    exit(-1)

def get_config_section(
    name: str, 
    alt: str | int | float | list | None = None
) -> str | int | float | list:
    '''
    This get section from root config file, or raise exception when section
    not exists, and alt is set to None. When alt is provided, and section
    not exists then return alt. If section exists, then return section.

    Parameters:
        name (str): Name of the section
        alt (str | int | float | list | None) = None: Alternative value

    Returns:
        (str | int | flaot | list): Value of the section
    '''

    global config

    if name in config:
        return config[name]

    if alt is not None:
        return alt

    print("Not found section \"" + name + "\" in config.")
    exit(-2)

def compile(*command: list) -> None:
    '''
    This function execute command, and when command not exit without
    error code, that mean not return 0, then it print STDOUT and STDERR
    from the command.

    Parameters:
        command (list): Command as list of str
    '''

    result = subprocess.run(command, capture_output = True)

    if result.returncode == 0:
        return 

    print(result.stdout.decode("UTF-8"))
    print(result.stderr.decode("UTF-8"))
    print("Compilation end with error.")
    exit(-5)

# Rendering app view
from source.make.render import render
from source.make.dom import link, script, style

app_view_render = render(app_view_file)

# App view use HTML description as replace tags
app_view_render.start_tag = "{{"
app_view_render.stop_tag = "}}"

view_params = get_config_section("view-params")

# Replace all elements from view-params config section
for param in view_params.keys():
    content = view_params[param]

    if type(content) is str:
        app_view_render.add(param, content)
        continue

    if type(content) is float or type(content) is int:
        app_view_render.add(param, str(content))
        continue

    print("Param " + param + " not contain string or number.")
    exit(-3)

# Compile loading screen sass file
compile(
    "sass",
    "--sourcemap=none",
    "-t compressed",
    str(sass_loading_screen_file),
    str(sass_loading_screen_tmp_file)
)

# Open compiled tmp file, add it to view, and drop tmp file
loading_screen = style()

with sass_loading_screen_tmp_file.open() as handle:
    loading_screen.content = handle.read()

sass_loading_screen_tmp_file.unlink()

# Generating tags
sass_link = link()
sass_link.rel = "stylesheet"
sass_link.type = "text/css"
sass_link.href = "./" + sass_loader_result_file.name + "?version=" + release

js_script = script()
js_script.src = "./" + script_loader_result_file.name + "?version=" + release

# Add Three.js
three_js = script()
three_js.src = "https://unpkg.com/three@0.149.0/build/three.min.js"

bundle_items = (
    sass_link.render() + 
    js_script.render() + 
    three_js.render() + 
    loading_screen.render()
)

# Add bundle items tags
app_view_render.add("bundle_items", bundle_items)

# Render and save app view
app_view = app_view_render.finalize()

try:
    with app_view_result_file.open("w") as handle:
        handle.write(app_view)
except event:
    print("Can not save app view.")
    print(str(event))
    exit(-4)

# Bundle JS script source
compile(
    "esbuild",
    str(script_loader_file),
    "--bundle",
    "--outfile=" + str(script_loader_result_file)
)

# Bundle SASS themes source
compile(
    "sass",
    "--sourcemap=none",
    "-t compressed",
    str(sass_loader_file),
    str(sass_loader_result_file)
)

# Copy static folder
compile(
    "cp",
    "-r",
    str(static_files),
    str(static_files_output)
)

# Copy config folder
compile(
    "cp",
    "-r",
    str(app_config), 
    str(output_config)
)

print("Build success!")