import argparse
import os
import sys
import tempfile
import webbrowser
from http.server import SimpleHTTPRequestHandler
from os import listdir, mkdir, symlink
from os.path import abspath, dirname, isabs, isdir, isfile, join
from shutil import rmtree
from socketserver import TCPServer
from subprocess import check_call


def _die(s):
    print(s, file=sys.stderr)
    exit(1)


def gen_main(input_dir, strict, src_func=None):
    """Main entry point for generation.
    Args:
        input_dir: Directory which contains initial input files.
        strict: Determines if Sphinx warnings should be interpreted as errors.
        src_func: (optional) Callable of form `f(src_dir)` which will introduce
            additional source files to `src_dir`.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--out_dir", type=str, required=True,
        help="Output directory. Does not have to exist beforehand.")
    parser.add_argument(
        "--debug", action="store_true",
        help="If enabled, leaves intermediate files that are otherwise "
             "deleted.")
    args = parser.parse_args()
    out_dir = args.out_dir

    if out_dir == "<test>":
        out_dir = join(os.environ["TEST_TMPDIR"], "doc")

    if not isabs(out_dir):
        _die("--out_dir must be absolute path: {}".format(out_dir))
    # Use default temp directory, handling both `bazel run` and `bazel test`.
    tmp_dir = tempfile.mkdtemp(dir=os.environ.get("TEST_TMPDIR"))
    doctree_dir = join(tmp_dir, "doctrees")
    src_dir = join(tmp_dir, "src")
    # Symlink inputs to src dir (so that we can also generate doc modules).
    mkdir(src_dir)

    for f in listdir(input_dir):
        src_f = join(src_dir, f)
        symlink(join(input_dir, f), src_f)
    # Optionally generate additional input files as source.

    if src_func:
        src_func(src_dir)
    print("Generating documentation...")

    if strict:
        # Turn warnings into errors; else be quiet.
        warning_args = ["-W", "-N", "-q"]
    else:
        warning_args = [
            "-N", "-Q",  # Be very quiet.
            "-T",  # Traceback (for plugin)
        ]
    check_call([
        sys.executable, "sphinx_build.py",
        "-b", "html",  # HTML output.
        "-a", "-E",  # Don't use caching.
        "-d", doctree_dir] + warning_args + [
        src_dir,  # Source dir.
        out_dir,
    ])

    if not args.debug:
        rmtree(tmp_dir)
    else:
        print("DEBUG: Temporary files: {}".format(tmp_dir))


class _Handler(SimpleHTTPRequestHandler):
    # An HTTP handler without logging.
    def log_request(*_):
        pass


def _str2bool(value):
    # From: https://stackoverflow.com/a/19233287/7829525

    return value.lower() in ("yes", "y", "true", "t", "1")


def preview_main(gen_script, default_port):
    """Main entrypoint for previewing documentation.
    Args:
        gen_script: Generation script, required to generate docs.
        default_port: Default port for local HTTP server.
    """
    parser = argparse.ArgumentParser()
    parser.register('type', 'bool', _str2bool)
    parser.add_argument(
        "--browser", type='bool', default=True, metavar='BOOL',
        help="Open browser. Disable this if you are frequently recompiling.")
    parser.add_argument(
        "--port", type=int, default=default_port, metavar='PORT',
        help="Port for serving doc pages with a HTTP server.")
    parser.add_argument(
        "--generates", type='bool', default=False, metavar='BOOL',
        help="Only generates.")
    args = parser.parse_args()

    if args.generates:
        from gen_sphinx import write_doc_modules
        write_doc_modules(dirname(gen_script))
    else:
        # Choose an arbitrary location for generating documentation.
        out_dir = abspath("_build")

        if isdir(out_dir):
            rmtree(out_dir)
        # Generate.
        check_call([sys.executable, gen_script, "--out_dir", out_dir])
        print("Sphinx preview docs are available at:")
        file_url = "file://{}".format(join(out_dir, "index.html"))

        browser_url = file_url
        print()
        print("  {}".format(file_url))
        # Serve the current directory for local browsing. Required for MacOS.
        # N.B. We serve the preview via a HTTP server because it is necessary for
        # certain browsers (Safari on MacOS, possibly Chrome) due to local file
        # restrictions.
        os.chdir(out_dir)
        sockaddr = ("127.0.0.1", args.port)
        TCPServer.allow_reuse_address = True
        httpd = TCPServer(sockaddr, _Handler)
        http_url = "http://{}:{}/index.html".format(*sockaddr)
        print()
        print("  {}".format(http_url))
        # Default to using HTTP serving only on MacOS; on Ubuntu, it can spit
        # out errors and exceptions about broken pipes, 404 files, etc.

        if sys.platform == "darwin":
            browser_url = http_url
        # Try the default browser.

        if args.browser:
            webbrowser.open(browser_url)
        # Wait for server.
        print()
        print("Serving and waiting ... use Ctrl-C to exit.")
        httpd.serve_forever()
