import logging
from pathlib import Path
from typing import Union

import moderngl

from moderngl_window.exceptions import ImproperlyConfigured
from moderngl_window.loaders.base import BaseLoader
from moderngl_window.opengl import program

logger = logging.getLogger(__name__)


class Loader(BaseLoader):
    kind = "single"
    meta: program.ProgramDescription

    def load(self) -> moderngl.Program:
        """Loads a shader program from a single glsl file.

        Each shader type is separated by preprocessors

        - VERTEX_SHADER
        - FRAGMENT_SHADER
        - GEOMETRY_SHADER
        - TESS_CONTROL_SHADER
        - TESS_EVALUATION_SHADER

        Example:

        .. code:: glsl

            #version 330

            #if defined VERTEX_SHADER

            in vec3 in_position;
            in vec2 in_texcoord_0;
            out vec2 uv0;

            void main() {
                gl_Position = vec4(in_position, 1);
                uv0 = in_texcoord_0;
            }

            #elif defined FRAGMENT_SHADER

            out vec4 fragColor;
            uniform sampler2D texture0;
            in vec2 uv0;

            void main() {
                fragColor = texture(texture0, uv0);
            }
            #endif

        Returns:
            moderngl.Program: The Program instance
        """
        prog: Union[moderngl.Program, program.ReloadableProgram]
        assert self.meta.path is not None, "There is no path for the resource"
        assert self.meta.path is not None, "There is no path for the resource"

        self.meta.resolved_path, source = self._load_source(self.meta.path)
        shaders = program.ProgramShaders.from_single(self.meta, source)
        shaders.handle_includes(self._load_source)
        prog = shaders.create()

        # Wrap the program if reloadable is set
        if self.meta.reloadable:
            # Disable reload flag so reloads will return Program instances
            self.meta.reloadable = False
            # Wrap it ..
            prog = program.ReloadableProgram(self.meta, prog)

        return prog

    def _load_source(self, path: Union[Path, str]) -> tuple[Path, str]:
        """Finds and loads a single source file.

        Args:
            path: Path to resource
        Returns:
            tuple[resolved_path, source]: The resolved path and the source
        """
        resolved_path = self.find_program(path)
        if not resolved_path:
            raise ImproperlyConfigured("Cannot find program '{}'".format(path))

        logger.info("Loading: %s", path)

        with open(str(resolved_path), "r") as fd:
            return resolved_path, fd.read()
