# frozen_string_literal: true

require 'cgi/util'
require 'digest/sha2'
require 'fileutils'
require 'fastimage'

SHA_DIR = 'sha'
ASSET_DIR = '_assets'

def rename_to_shasum(context, path)
  extname = File.extname(path)

  hexname = Digest::SHA2.file(path).hexdigest

  cache_dir = File.join('.jekyll-cache', 'sha_files')
  FileUtils.makedirs(File.join(cache_dir, SHA_DIR))

  base_shaname = "#{hexname}#{extname}"
  cache_fname = File.join(cache_dir, SHA_DIR, base_shaname)

  # First check that the file hasn't already been added to the static files.
  # Note: Duplicates will cause a ruckus here, and this is "accidentally"
  # quadratic. Need to figure out how to add a hash mapping of shasum files to
  # the context if this becomes too slow.
  site = context.registers[:site]
  rel_path = File.join(SHA_DIR, base_shaname)
  already_added = false

  for file in site.static_files
    if file.relative_path == rel_path
      already_added = true
      break
    end
  end

  # Must delete and recreate if already exists, as we could've moved the content
  # of two files around, causing the shasum to point to stale files.
  if File.exists?(cache_fname)
    FileUtils.rm(cache_fname)
  end
  FileUtils.link_entry(path, cache_fname)


  if not already_added
    site.static_files << Jekyll::StaticFile.new(site, cache_dir, SHA_DIR, base_shaname)
  end

  return "/#{SHA_DIR}/#{base_shaname}"
end

def prefix_file_dir_to_input(context, input)
  raw_name = context.registers[:page]['path']
  folder_dir = File.basename(raw_name, '.*')

  return folder_dir + File::Separator + input
end

# ShaFileTag takes the file and computes its shasum, then dumps its sha-256 name
# to the sha output folder.
#
# Example usage within a page:
# {% <img src="{% shafile myimage.jpg %}" alt="My alt" title="My title" /> %}
#
# The root directory will be considered to be ASSET_DIR.
class ShaFileTag < Liquid::Tag
  def initialize(tag_name, input, tokens)
    super
    @path = input.strip
  end

  def render(context)
    CGI.escape_html rename_to_shasum(context, File.join(ASSET_DIR, @path))
  end
end
Liquid::Template.register_tag('shafile', ShaFileTag)


# LocalShaFileTag works like ShaFileTag, but expands the filename from
# file.ext to _imgs/the-post-name/file.ext. Example:
#
# Doing {% lshafile myfile.txt %}
#
# within the file _posts/2001-01-01-mypost.md would be equivalent to
#
# {% shafile _assets/2001-01-01-mypost/myfile.txt %}
class LocalShaFileTag < ShaFileTag
  def render(context)
    @path = prefix_file_dir_to_input(context, @path)
    super
  end
end
Liquid::Template.register_tag('lshafile', LocalShaFileTag)

def image_mimetype(ext)
  case ext
  when '.jpg', '.jpeg'
    return 'image/jpeg'
  when '.png'
    return 'image/png'
  else
    raise "image_mimetype: unknown extension #{ext}"
  end
end

# ShaImageTag takes the file and computes its shasum, then dumps its sha-256
# name to the sha output folder. There must be two or three arguments separated
# by a pipe.
#
# Example usage within a page:
# {% shaimg image.jpg | My title | My alt %}
# or
# {% shaimg image.jpg | My title and alt %}
class ShaImageTag < Liquid::Tag
  def initialize(tag_name, input, tokens)
    super
    @input = input
  end

  def parse_args(context)
    args = @input.strip.split('|')
    if args.length != 2 and args.length != 3
      bad_file = context.registers[:page]['path']
      err_msg = "On #{bad_file}: '#{@input}' does not contain 2 or 3 arguments"
      raise err_msg
    end
    ret = Hash.new
    ret[:path] = args[0].strip
    ret[:title] = args[1].strip
    ret[:alt] = ret[:title]
    if args.length == 3
      ret[:alt] = args[2].strip
    end
    return ret
  end

  def render(context)
    args = parse_args(context)

    fname = File.join(ASSET_DIR, args[:path])

    extname = File.extname(fname)
    original_mimetype = image_mimetype(extname)

    source_tags = []

    src = CGI.escape_html rename_to_shasum(context, fname)
    orig_width, orig_height = FastImage.size(fname, :raise_on_failure=>true)
    alt = CGI.escape_html args[:alt]
    title = CGI.escape_html args[:title]
    orig_src = "<source srcset=\"#{src}\" type=\"#{original_mimetype}\" width=\"#{orig_width}\" height=\"#{orig_height}\">"
    source_tags.push(orig_src)

    img_tag = "<img src=\"#{src}\" alt=\"#{alt}\" title=\"#{title}\" width=\"#{orig_width}\" height=\"#{orig_height}\">"

    # see if there's a .webp file for this original file:
    webp_fname = fname[0..-extname.length()-1] + '.webp'
    has_webp = File.file?(webp_fname)
    if has_webp
      webp_src = CGI.escape_html rename_to_shasum(context, webp_fname)
      webp_width, webp_height = FastImage.size(webp_fname, :raise_on_failure=>true)
      webp_src = "<source srcset=\"#{webp_src}\" type=\"image/webp\" width=\"#{webp_width}\" height=\"#{webp_height}\">"
      source_tags.push(webp_src)
    end

    avif_fname = fname[0..-extname.length()-1] + '.avif'
    has_avif = File.file?(avif_fname)
    if has_avif
      avif_src = CGI.escape_html rename_to_shasum(context, avif_fname)
      avif_width, avif_height = FastImage.size(avif_fname, :raise_on_failure=>true)
      avif_src = "<source srcset=\"#{avif_src}\" type=\"image/avif\" width=\"#{avif_width}\" height=\"#{avif_height}\">"
      source_tags.push(avif_src)
    end

    if source_tags.length == 1
      Jekyll.logger.warn "#{fname} has no .webp/.avif equivalent. Consider creating one"
      return img_tag
    end

    return "<picture>#{source_tags.reverse.join}#{img_tag}</picture>"
  end
end
Liquid::Template.register_tag('shaimg', ShaImageTag)

# LocalShaImageTag works like ShaImageTag, but expands the filename from
# file.png to _imgs/the-post-name/file.png. Example:
#
# {% lshaimg image.jpg | My title | My alt %}
#
# within the file _posts/2001-01-01-mypost.md would be equivalent to
#
# {% shaimg _assets/2001-01-01-mypost/image.jpg | My title | My alt %}
class LocalShaImageTag < ShaImageTag
  def parse_args(context)
    @input = prefix_file_dir_to_input(context, @input)
    super
  end
end
Liquid::Template.register_tag('lshaimg', LocalShaImageTag)

# LocalIncludeTag bundles the file given as input: The content is statically
# included (... I think?)
#
# The call {% linclude my-svg.svg %} on the post my-post.md will include the
# file located at _asset/my-post/my-svg.svg.
class LocalIncludeTag < Liquid::Tag
  def initialize(tag_name, input, tokens)
    super
    @input = input.strip
  end

  def render(context)
    input = File.join(ASSET_DIR, prefix_file_dir_to_input(context, @input))

    return File.read(input)
  end
end
Liquid::Template.register_tag('linclude', LocalIncludeTag)