"""
Does every target has a preferred format?
  HTML - JPEG (or SVG if available?)
  LaTeX - PDF or EPS?
  Plaintext - A URL? 
  EPUB - JPEG?
"""

import sys, os, glob, shutil, subprocess, re, codecs
import filecmp
import pdf_image
import kutil

import OpenImageIO as oiio

def find_ext(filenames, ext):
    for f in filenames:
        if f.endswith(ext):
            return f
    return None

def cache_update_required(source, cached):
    return cached is None \
        or not(os.path.exists(cached)) \
        or (os.path.getmtime(cached) < os.path.getmtime(source))


def image_dimensions(filename):
    resize = True
    if filename.endswith(".pdf"):
        width, height = pdf_image.dimensions(filename)
        resize = False
    else:
        inp = oiio.ImageInput.open(filename)
        if not inp:
            raise Exception(f'Cannot read image "{filename}": {oiio.geterror()}')
        spec = inp.spec()
        width, height = spec.width, spec.height
        inp.close()
    return width, height, resize


class Image_cache:
    max_width = 1600 # Should be a state variable
    cache_basedir = "_klammertext_imgsrc" # Should be a state variable
    error_marker_filename = "_error_marker.png"
    verbose_label = "     [klammertext]" # Should parameterize verbosity
    def __init__(self, cache_dir, search_path, verbose=False):
        #self.cache_dir = kutil.cache_directory(
        self.dir = kutil.cache_directory(cache_dir, "_image_cache")
        self.search = search_path.split(" ")
        self.verbose = verbose or os.environ.get("KLAMMERTEXT_IMAGE_CACHE_DISPLAY")
        self.required_format = { "html"  : ["jpg", "jpeg", "JPEG", "png", "gif", "webp"],
                                 "pdf"   : ["png", "jpg", "jpeg", "JPEG", "pdf", "eps"],
                                 "tex"   : ["png", "jpg", "jpeg", "JPEG", "pdf", "eps"],
                                 "latex" : ["png", "jpg", "jpeg", "JPEG", "pdf", "eps"] }
        self.error_image_filename = f"{self.dir}/{Image_cache.error_marker_filename}"
        if not os.path.exists(self.dir):
            os.makedirs(self.dir)
        self.search = self.search + [f"{os.path.dirname(cache_dir)}/imgsrc"]

    def make_error_marker(self):
        if not os.path.exists(self.error_image_filename):
            err_buf = oiio.ImageBuf(oiio.ImageSpec(300, 100, 3, oiio.UINT8))
            oiio.ImageBufAlgo.fill(err_buf, (1.0, 0.0, 0.0))
            err_buf.write(self.error_image_filename)

    def construct_cache_filename(self, target, filename):
        basename, ext = os.path.splitext(filename)
        required = self.required_format[target]
        if ext in self.required_format[target]:
            cache_ext = ext
        else:
            cache_ext = required[0]
        return f"{self.dir}/{filename}.{cache_ext}"


    def find_file(self, target, basename):
        result = None
        candidates = []
        matches = []
        for path in self.search:
            candidates += glob.glob(f"{path}/{basename}.*")
        candidates = sorted(candidates, key=lambda p: 0 if p.endswith(".png") else 1)
        if False:
            print("Candidates:")
            for c in candidates:
                print(f" {c}")
        if candidates:
            matches = []
            for ext in self.required_format[target]:
                candidate = find_ext(candidates, ext)
                if candidate:
                    matches.append(candidate)
            if not matches:
                result = candidates[0]
        else:
            kutil.msg(kutil.red_text(f'No image with basename "{basename}" found in search path:'))
            kutil.msg(kutil.red_text("  | " + " | ".join(self.search) + " | "))
            self.make_error_marker()
            matches = [self.error_image_filename]

        result = matches[0] if matches else candidates[0]
        """
        if len(matches) > 1:
            target = f"for .{target} format"
            print(f'{Image_cache.verbose_label}  Warning: Multiple matches for "{basename}" {target}:')
            sp = " "*len(Image_cache.verbose_label)
            for f in matches:
                print(f"{sp}    {f}")
        """
        return result


    def find_cached_file(self, target, basename):
        exts = self.required_format.get(target)
        if exts is None:
            raise Exception(
                f'No preferred image format defined for target "{target}"')
        result = None
        for ext in exts:
            cached_filename = f"{self.dir}/{basename}.{ext}"
            if os.path.exists(cached_filename):
                return cached_filename
        return None

        
    def describe_caching(self, source_filename, cached_filename, copied,
                         width, height, new_width, new_height):
        src = os.path.relpath(source_filename)
        if src[:2] == "..":
            src = source_filename
        dest = os.path.splitext(cached_filename)[1]
        dim = "" if width == new_width else f" [{new_width}x{new_height}]"
        desc = "(copied)" if copied else f"-> {dest}{dim}"
        #print(f"{Image_cache.verbose_label}    To cache: {src} [{width}x{height}] {desc}")
        kutil.msg("To cache: {src} [{width}x{height}] {desc}")

    def update_cache(self, target, basename, source_filename):
        cached_filename = self.construct_cache_filename(target, basename)
        width, height, resize = image_dimensions(source_filename)
        new_width = width
        new_height = height

        source_format = os.path.splitext(source_filename)[-1][1:]
        #print("Source:", source_format, basename)
        if source_format in self.required_format[target]:
            cached_filename = f"{os.path.dirname(cached_filename)}/{os.path.basename(source_filename)}"

        copied = False
        if resize and width > Image_cache.max_width:
            new_width = Image_cache.max_width
            new_height = int(round(new_width * float(height) / float(width)))
            if source_filename.endswith(".pdf"):
                pdf_image.convert(source_filename, cached_filename)
                buf = oiio.ImageBuf(cached_filename)
            else:
                buf = oiio.ImageBuf(source_filename)
            roi = oiio.ROI(0, new_width, 0, new_height, 0, 1, 0, buf.nchannels)
            resized = oiio.ImageBufAlgo.resize(buf, roi=roi)
            resized.write(cached_filename)
        elif source_format in self.required_format[target]:
            #print("COPY", source_filename, cached_filename)
            if not os.path.exists(cached_filename) or \
               not filecmp.cmp(source_filename, cached_filename, shallow=False):
                shutil.copy2(source_filename, cached_filename)
            copied = True
        else:
            try:
                if source_filename.endswith(".pdf"):
                    pdf_image.convert(source_filename, cached_filename)
                else:
                    buf = oiio.ImageBuf(source_filename)
                    if not buf.has_error:
                        buf.write(cached_filename)
                    else:
                        raise Exception(buf.geterror())
            except Exception as err:
                print(f'Cannot convert "{source_filename}" to '
                      f'"{os.path.splitext(cached_filename)[-1][1:]}":')
                print("   ", err)
                sys.exit(1)

        if self.verbose:
            self.describe_caching(source_filename, cached_filename, copied,
                                  width, height, new_width, new_height)
        return cached_filename, new_width, new_height


    def get(self, target, basename):
        cached_filename = self.find_cached_file(target, basename)
        source_filename = self.find_file(target, basename)
        if cache_update_required(source_filename, cached_filename):
            cached_filename, width, height = self.update_cache(target, basename, source_filename)
        else:
            width, height, _ = image_dimensions(cached_filename)
        if self.verbose:
            base = os.path.basename(cached_filename)
            #print(f"{Image_cache.verbose_label}  From cache: {base} [{width}x{height}]")
            kutil.msg("From cache: {base} [{width}x{height}]")
        # print("image_cache:", cached_filename, Image_cache.error_marker_filename)
        return cached_filename, width, height, cached_filename == Image_cache.error_marker_filename

    def clear(self):
        shutil.rmtree(self.dir)
        os.makedirs(self.dir)
            
if __name__ == '__main__':

    def getter(format, basename):
        C.get(format, basename)
        C.get(format, basename)

    def label(format=""):
        print("-"*80)
        print(format)

    home = os.environ.get("HOME")
    C = Image_cache(f"{home}",
        f"{home}/projects/klammertext/K/doc/handbook/imgsrc".split())
    a = "heringsdorf_stairwell"
    b = "usedom_clouds"

    C.clear()
    label("HTML")
    getter("html", a)
    getter("html", b)
    label("LATEX")
    getter("latex", a)
    getter("latex", b)
    label()
        
