#!/usr/bin/env python3

import codecs
import logging
import os
from shutil import copyfile

import jinja2

logging.basicConfig()
log = logging.getLogger("build")
log.setLevel(logging.INFO)

class App(object):
    # Global template variables.
    def vars(self):
        return {
            "site": {
                "name": "noah.is",
            },
        }

    def __init__(self):
        # Jinja environment.
        self.env = jinja2.Environment(
            loader=jinja2.FileSystemLoader("./templates"),
        )

        # Collect all the templates and all the static files.
        self.templates = self.crawl("./templates") # TODO: remove list() wrapper
        self.static    = self.crawl("./static")

        # Copy all of the static files into the public root.
        if not os.path.isdir("./public"):
            self.mkdir("./public")
        for static in self.static:
            public = static.replace("./static/", "./public/")
            self.mkdir(public.rsplit("/", 1)[0])  # ensure the path will exist
            if self.should_copy_static(static, public):
                log.info("COPY STATIC FILE %s -> %s", static, public)
                copyfile(static, public)

        # And render all of the templates.
        for template in self.templates:
            if template.startswith("./templates/layout"): # don't create these
                continue
            public = self.template_to_public(template)
            self.mkdir(public.rsplit("/", 1)[0])  # ensure the path will exist
            log.info("RENDER TEMPLATE: %s -> %s", template, public)
            self.render_template(template, public)

    def render_template(self, template, public):
        """
        Render a template to the public path.

        Parameters:
            template (str): path in ./templates
            public (str): path in ./public
        """
        tpl = self.env.get_template(template.replace("./templates/", ""))
        vars = self.vars()
        vars["template"] = template
        vars["filename"] = template.split("/")[-1]
        html = tpl.render(vars)

        with codecs.open(public, "w", "utf-8") as fh:
            fh.write(html)


    def crawl(self, path):
        """
        Crawl a directory recursively and return a list of file paths.

        Parameters:
            path (str)

        Returns:
            filepaths (list[str])
        """
        for root, dirs, files in os.walk(path):
            for file in files:
                yield os.path.join(root, file)

    def should_copy_static(self, static_file, public_file):
        """
        Determine if a static file should be copied over to the public folder.

        This means the public file either does not exist yet, or is older
        than the static file and so the new static file should be copied over it.

        Parameters:
            static_file (str)
            public_file (str)

        Returns:
            bool: True if public_file doesn't exist or is older than static_file.
        """
        if not os.path.exists(public_file):
            return True
        return os.stat(static_file).st_mtime > os.stat(public_file).st_mtime

    def template_to_public(self, template_filename):
        """
        Convert a template filename into a public name, preferring clean URL
        paths without file extension suffixes.

        Examples:
        * /index.html -> /index.html
        * /about.html -> /about/index.html
        * /photos/index.html -> /photos/index.html
        * /photos/2018.html  -> /photos/2018/index.html

        The `.html` and `.md` file types will suppress suffixes in this way.

        Parameters:
            template_filename: like ``./templates/about.html``

        Returns:
            public_filename: like ``./public/about/index.html``
        """
        public = template_filename.replace("./templates/", "./public/")
        path, filename = public.rsplit("/", 1)
        basename, ext = filename.rsplit(".", 1)
        if ext not in ["html", "md"]:
            # Not a web page so just keep the path literal.
            return public

        print("template_to_public:", template_filename, public)

        # See if it's already an index page or if we need to create one.
        if filename in ["index.html", "index.md"]:
            return public  # already a good name
        else:
            print("HERE:", os.path.join(path, basename, "index."+ext))
            log.info("basename=%r  path=%r  filename=%s", basename, path, filename)
            return os.path.join(path, basename, "index."+ext)

    def mkdir(self, path):
        """Create a directory and log it."""
        if not os.path.isdir(path):
            log.info("mkdir: %s", path)
            os.makedirs(path, mode=0o755)

if __name__ == "__main__":
    App()
