import datetime
import os
import re
from typing import Optional, List, Tuple, Type

from flask import Flask, abort
from markdown import markdown

from .config import config
from .latex import MarkdownLatex
from ._sorters import PagesSorter, PagesSortByTime


class BlogApp(Flask):
    _title_header_regex = re.compile(r"^#\s*((\[(.*)\])|(.*))")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, template_folder=config.templates_dir, **kwargs)
        self.pages_dir = os.path.join(config.content_dir, "markdown")
        self.img_dir = config.default_img_dir
        self.css_dir = config.default_css_dir
        self.js_dir = config.default_js_dir
        self.fonts_dir = config.default_fonts_dir

        if not os.path.isdir(self.pages_dir):
            # If the `markdown` subfolder does not exist, then the whole
            # `config.content_dir` is treated as the root for markdown files.
            self.pages_dir = config.content_dir

        img_dir = os.path.join(config.content_dir, "img")
        if os.path.isdir(img_dir):
            self.img_dir = os.path.abspath(img_dir)
        else:
            self.img_dir = config.content_dir

        css_dir = os.path.join(config.content_dir, "css")
        if os.path.isdir(css_dir):
            self.css_dir = os.path.abspath(css_dir)

        js_dir = os.path.join(config.content_dir, "js")
        if os.path.isdir(js_dir):
            self.js_dir = os.path.abspath(js_dir)

        fonts_dir = os.path.join(config.content_dir, "fonts")
        if os.path.isdir(fonts_dir):
            self.fonts_dir = os.path.abspath(fonts_dir)

        templates_dir = os.path.join(config.content_dir, "templates")
        if os.path.isdir(templates_dir):
            self.template_folder = os.path.abspath(templates_dir)

    def get_page_metadata(self, page: str) -> dict:
        if not page.endswith(".md"):
            page = page + ".md"

        md_file = os.path.join(self.pages_dir, page)
        if not os.path.isfile(md_file):
            abort(404)

        metadata = {}
        with open(md_file, "r") as f:
            metadata["uri"] = "/article/" + page[:-3]

            for line in f:
                if not line:
                    continue

                if not (m := re.match(r"^\[//]: # \(([^:]+):\s*(.*)\)\s*$", line)):
                    break

                if m.group(1) == "published":
                    metadata[m.group(1)] = datetime.datetime.fromisoformat(
                        m.group(2)
                    ).date()
                else:
                    metadata[m.group(1)] = m.group(2)

        if not metadata.get("title"):
            # If the `title` header isn't available in the file,
            # infer it from the first line of the file
            with open(md_file, "r") as f:
                header = ""
                for line in f.readlines():
                    header = line
                    break

            metadata["title_inferred"] = True
            m = self._title_header_regex.search(header)
            if m:
                metadata["title"] = m.group(3) or m.group(1)
            else:
                metadata["title"] = os.path.basename(md_file)

        if not metadata.get("published"):
            # If the `published` header isn't available in the file,
            # infer it from the file's creation date
            metadata["published"] = datetime.date.fromtimestamp(
                os.stat(md_file).st_ctime
            )
            metadata["published_inferred"] = True

        return metadata

    def get_page(
        self,
        page: str,
        title: Optional[str] = None,
        skip_header: bool = False,
        skip_html_head: bool = False,
    ):
        if not page.endswith(".md"):
            page = page + ".md"

        metadata = self.get_page_metadata(page)
        # Don't duplicate the page title if it's been inferred
        if not (title or metadata.get("title_inferred")):
            title = metadata.get("title", config.title)

        with open(os.path.join(self.pages_dir, page), "r") as f:
            return render_template(
                "article.html",
                config=config,
                title=title,
                url=metadata.get("uri"),
                image=metadata.get("image"),
                description=metadata.get("description"),
                author=(
                    re.match(r"(.+?)\s+<([^>]+>)", metadata["author"])[1]
                    if "author" in metadata
                    else None
                ),
                author_email=(
                    re.match(r"(.+?)\s+<([^>]+)>", metadata["author"])[2]
                    if "author" in metadata
                    else None
                ),
                published=(
                    metadata["published"].strftime("%b %d, %Y")
                    if metadata.get("published")
                    and not metadata.get("published_inferred")
                    else None
                ),
                content=markdown(
                    f.read(),
                    extensions=["fenced_code", "codehilite", "tables", MarkdownLatex()],
                ),
                skip_header=skip_header,
                skip_html_head=skip_html_head,
            )

    def get_pages(
        self,
        with_content: bool = False,
        skip_header: bool = False,
        skip_html_head: bool = False,
        sorter: Type[PagesSorter] = PagesSortByTime,
        reverse: bool = True,
    ) -> List[Tuple[int, dict]]:
        pages_dir = app.pages_dir.rstrip("/")
        pages = [
            {
                "path": os.path.join(root[len(pages_dir) + 1 :], f),
                "folder": root[len(pages_dir) + 1 :],
                "content": (
                    self.get_page(
                        os.path.join(root, f),
                        skip_header=skip_header,
                        skip_html_head=skip_html_head,
                    )
                    if with_content
                    else ""
                ),
                **self.get_page_metadata(os.path.join(root[len(pages_dir) + 1 :], f)),
            }
            for root, _, files in os.walk(pages_dir, followlinks=True)
            for f in files
            if f.endswith(".md")
        ]

        sorter_func = sorter(pages)
        pages.sort(key=sorter_func, reverse=reverse)
        return [(i, page) for i, page in enumerate(pages)]


app = BlogApp(__name__)


from .routes import *


# vim:sw=4:ts=4:et:
