import os
import base64
import textwrap
from pathlib import Path
from io import BytesIO

import numpy as np

from .component import (
    Component,
    QuitButton,
    CaptureButton,
    ControlPanel,
    VeuxTable,
)

_BASE_PATH = Path(__file__).parents[0]


class Viewer:
    """
    A class to represent a 3D model viewer.

    Methods:
    --------
    __init__(self, viewer=None, path=None, data=None):
        Initializes the Viewer with optional viewer type, file path, or binary data.
    """

    def __init__(self, thing,
                 viewer=None, id=None,
                 plane=False,
                 size=None,
                 hosted=None,
                 show_quit=True,
                 quit_on_load=False,
                 standalone=True,
                 lights=None):
        """
        If hosted is True, the viewer will fetch the model.
        If standalone is True, the viewer will be rendered as a standalone HTML page.
        """
        self._id = id if id is not None else "veux-viewer"
        self._viewer = viewer if viewer is not None else os.environ.get("VEUX_VIEWER", "mv")
        self._lights = lights
        self._plane = plane
        self._size = size
        self._show_quit = show_quit
        self._quit_on_load = quit_on_load

        self._hosted = hosted
        if hosted is None and self._viewer == "mv":
            self._hosted = False
        elif hosted is None:
            self._hosted = True

        if hasattr(thing, "canvas"):
            canvas = thing.canvas
            data = canvas.to_glb()
        elif hasattr(thing, "to_glb"):
            canvas = thing
            data = canvas.to_glb()
        else:
            data = thing

        self._standalone = standalone

        if not self._hosted:
            self._model_data = None
            data64 = base64.b64encode(data).decode('utf-8')
            self._glbsrc = f"data:model/gltf-binary;base64,{data64}"
        else:
            self._model_data = data
            self._glbsrc = "/model.glb"


        self._components = self._build_components()

    def _build_components(self):
        """Assemble list of UI component."""
        components = []

        if self._viewer == "mv":
            if self._show_quit:
                components.append(QuitButton(quit_on_load=self._quit_on_load))
                components.append(CaptureButton())
            # Control panel is always enabled for the mv viewer
            components.append(ControlPanel(_BASE_PATH))

        return components

    def resources(self):
        if self._hosted:
            yield ("/model.glb", lambda: self._model_data)

            if self._viewer == "mv":
                yield ("/black_ground.hdr", _serve_black_ground_hdr)

    def get_html(self):
        if self._viewer == "babylon":
            with open(_BASE_PATH / "babylon.html", "r") as f:
                return f.read()

        if self._viewer in {"three-170", "three"}:
            with open(_BASE_PATH / "three-170.html", "r") as f:
                return f.read()

        if self._viewer == "three-160":
            with open(_BASE_PATH / "three-160.html", "r") as f:
                return f.read()

        elif self._viewer == "three-130":
            with open(_BASE_PATH / "three-130.html", "r") as f:
                return f.read()

        elif self._viewer == "mv":
            return _model_viewer(
                self._glbsrc,
                components=self._components,
                plane=self._plane,
                size=self._size,
                hosted=self._hosted,
                light_mode=self._lights,
                standalone=self._standalone,
            )


# 
#  Page assembly
# 

def _model_viewer(source,
                  components=None,
                  size=None,
                  hosted=False,
                  plane=False,
                  standalone=True,
                  light_mode=None):

    components = components or []

    if light_mode is None:
        light_mode = "light"

    # Gather component contributions
    comp_css = "\n".join(c.css() for c in components)
    comp_html = "\n".join(c.html() for c in components)
    comp_js = "\n".join(c.js() for c in components)

    # Load the model-viewer library
    with open(_BASE_PATH / "model-viewer.min.js", "r") as f:
        library = f'<script type="module">{f.read()}</script>'

    # Environment
    if hosted:
        environment = "/black_ground.hdr"
    else:
        environment = "neutral"

    # Camera settings
    camera = """
        camera-controls
        max-field-of-view="90deg"
    """

    # Size
    if size is None:
        size_attr = 'style="width: 100%; height: 100vh;"'
    else:
        size_attr = f'style="width: {size[0]}px; height: {size[1]}px;"'

    # Core model-viewer element
    viewer_element = f"""
        <model-viewer
          id="veux-viewer"
          alt="rendering"
          src="{source}"
          autoplay
          {size_attr}
          max-pixel-ratio="2"
          interaction-prompt="none"
          shadow-intensity="1"
          environment-image="{environment}"
          shadow-light="10000 10000 10000"
          exposure="0.8"
          {camera}
          touch-action="pan-y">
          <div class="progress-bar hide" slot="progress-bar">
              <div class="update-bar"></div>
          </div>
        </model-viewer>
    """

    if not standalone:
        page = f"""
        <div style='display: flex; flex-direction: row;'>
          {comp_html}
          {viewer_element}
        </div>
        {library}
        <script>{comp_js}</script>
        """
    else:
        page = f"""
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
            <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <title>veux</title>
            {library}
            <style>{comp_css}</style>
            </head>
            <body>
                {comp_html}
                {viewer_element}
            </body>
            <script>{comp_js}</script>
        </html>
        """
    return textwrap.dedent(page)


#
#  HDR environment helper
#

def _serve_black_ground_hdr():
    import bottle
    width, height = 1024, 512

    hdr_image = np.ones((height, width, 3), dtype=np.float32)
    horizon = int(height * 0.6)
    hdr_image[horizon:, :] = 0.0

    hdr_header = (
        "#?RADIANCE\n"
        "FORMAT=32-bit_rle_rgbe\n\n"
        f"-Y {height} +X {width}\n"
    )

    rgbe_image = np.zeros((height, width, 4), dtype=np.uint8)
    brightest = np.maximum.reduce(hdr_image, axis=2)
    nonzero_mask = brightest > 0
    mantissa, exponent = np.frexp(brightest[nonzero_mask])
    rgbe_image[nonzero_mask, :3] = (hdr_image[nonzero_mask] / mantissa[:, None] * 255).astype(np.uint8)
    rgbe_image[nonzero_mask, 3] = (exponent + 128).astype(np.uint8)

    hdr_data = BytesIO()
    hdr_data.write(hdr_header.encode('ascii'))
    hdr_data.write(rgbe_image.tobytes())

    return bottle.HTTPResponse(
        body=hdr_data.getvalue(),
        status=200,
        headers={"Content-Type": "image/vnd.radiance"},
    )