# Copyright (c) 2018(-2024) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.24.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'fileutils'

def root_dir
  # Get the dirname of this (videoconvert.rb) file:
  @root_dir ||= File.dirname(__FILE__)
end

class Main
  def self.banner
    <<-BANNER
Convert video files.

Usage: #{File.basename($0)} {root_folder} asset_folder generated_folder

Example: #{File.basename($0)} assets/videos generated/videos
         will process files in assets/videos and place the result in generated/videos

Example: #{File.basename($0)} /c/Project assets/videos generated/videos
         will process files in /c/Project/assets/videos and place the result in /c/Project/generated/videos
BANNER
  end

  def self.write_file(file_name, content)
    FileUtils.mkdir_p(File.dirname(file_name))
    unless File.exist?(file_name) && content == File.open(file_name, 'r') { |f| f.read() }
    puts "Generating #{file_name}"
      File.open(file_name, 'w') { |f| f.write(content) }
    end
  end


  def self.bin_to_obj(bin, out_dir)

    symbol_file_name = "#{out_dir}/obj/rename_symbols.txt"
    bin_file_name = File.basename(bin)

    puts "Generating elf32 object from binary file #{out_dir}/bin/#{bin_file_name}..."
    `arm-none-eabi-objcopy.exe -B arm --rename-section .data=ExtFlashSection,contents,alloc,load,readonly,data -I binary -O elf32-littlearm \"#{out_dir}/bin/#{bin_file_name}\" \"#{out_dir}/obj/#{bin_file_name}.o\"`

    #Rename the symbols
    output_folder_name_subst = "#{out_dir+'/bin'}".gsub('.', '_').gsub('/', '_')
    bin_file_name_subst = File.basename(bin).gsub('.', '_').gsub('/', '_')
    str = output_folder_name_subst + "_" + bin_file_name_subst

    content =
        "_binary_#{str}_start video_#{bin_file_name_subst}_start\n" +
        "_binary_#{str}_end video_#{bin_file_name_subst}_end\n" +
        "_binary_#{str}_size video_#{bin_file_name_subst}_size\n"

    puts "Renaming symbols..."
    File.open(symbol_file_name, 'w') { |f| f.write(content) }

    # Bulk rename using objcopy --redefine-syms
    `arm-none-eabi-objcopy.exe --redefine-syms #{symbol_file_name} \"#{out_dir}/obj/#{bin_file_name}.o\"`

    #Remove temp file
    FileUtils.rm(symbol_file_name)
  end

  root_dir = '.'
  if ARGV.count == 3
    root_dir = ARGV.shift
  end

  if ARGV.count != 2
    abort self.banner
  end

  Dir.chdir(root_dir) do
    avi_dir = ARGV.shift.gsub('\\', '/')
    out_dir = ARGV.shift.gsub('\\', '/')
    bin_dir = File.join(out_dir, 'bin')
    obj_dir = File.join(out_dir, 'obj')
    avis = Dir[File.join(avi_dir, '**', '*.avi')]

    # Create the output folder:
    FileUtils.mkdir_p(bin_dir)

    # Create mapping from existing .avi files to the .bin files
    avi2bin = {}
    avi2obj = {}
    obj2avi = {}
    bin2avi = {}

    avis.each do |avi|
      bin = File.join(bin_dir, File.basename(avi.gsub(/avi$/i,'bin')))
      obj = File.join(obj_dir, File.basename(avi.gsub(/avi$/i,'bin.o')))
      avi2bin[avi] = bin
      avi2obj[avi] = obj

      if bin2avi[bin]
        abort "Duplication avi files: #{bin2avi[bin]} and #{avi}"
      end

      bin2avi[bin] = avi
      obj2avi[obj] = avi
    end

    # Remove bin files that have no corresponding avi file
    Dir[File.join(bin_dir, '*.bin')].each do |bin|
      if !bin2avi[bin]
        puts "Removing #{bin}"
        FileUtils.rm_f(bin)
      end
    end

    # Remove obj files that have no corresponding avi file
    Dir[File.join(obj_dir, '*.bin.o')].each do |obj|
      if !obj2avi[obj]
        puts "Removing #{obj}"
        FileUtils.rm_f(obj)
      end
    end

    # Now process each avi file
    avi2bin.each do |avi,bin|
      if !File.exist?(bin) || (File.mtime(avi) > File.mtime(bin))
        puts "Generating #{bin}"
        FileUtils.cp(avi, bin)
      end
    end

    # Convert .bin to .obj
    FileUtils.mkdir_p("#{out_dir}/obj")
    avi2obj.each do |avi, obj|
      bin = avi2bin[avi]

      if !File.exist?(obj) || (File.mtime(bin) > File.mtime(obj))
        puts "Converting #{bin} to #{obj}"
        bin_to_obj(bin, out_dir)
      end
    end

    # Locate .ioc file
    project_file = Dir['../*.ioc'].first
    if project_file.nil? # search in next parent directory if .ioc file not found
      project_file = Dir['../../*.ioc'].first
    end

    # Determine if keil is toolchain and structure of project
    touchgfx_folder_path = "../TouchGFX"
    toolchain_is_keil = false # Assume it is not Keil
    dual_core = false # Assume it is not dual core
    context_enabled = false # Assume not context enabled
    trust_zone = false # Assume not trust zone
    if project_file
      # From cubemx_project_selector.rb in touchgfx-cli:
      content = File.read(project_file, :encoding=>'utf-8')
      target_toolchain = content.match(/ProjectManager.TargetToolchain=([\w\s\d\.\-]+)/) || abort("Unable to parse #{File.expand_path(@project_file)}")
      toolchain = target_toolchain.captures.first
      toolchain_is_keil = toolchain.match(/MDK-ARM/) # "ProjectManager.TargetToolchain=MDK-ARM..." is Keil

      # Determine if dual core
      # From cubemx_project_selector.rb in touchgfx-cli:
      content = File.read(project_file, :encoding=>'utf-8')
      dual_core = content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_M(\d+)/)
      if dual_core then
        touchgfx_folder_path = "../CM#{$1}/TouchGFX" 
      end

      # Determine if context enabled project
      # From cubemx_project_selector.rb in touchgfx-cli:
      content = File.read(project_file, :encoding=>'utf-8')
      context_enabled = content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_App/)
      if context_enabled then
        touchgfx_folder_path = "../../Appli/TouchGFX" 
      end

      # Determine if trust zone (non secure only) project
      # From cubemx_project_selector.rb in touchgfx-cli:
      content = File.read(project_file, :encoding=>'utf-8')
      trust_zone = content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_M\d+NS/)
      if trust_zone
        # match expression is for dual is also true for trust zone, so we must disable dual core
        dual_core = false
        touchgfx_folder_path = "../NonSecure/TouchGFX" 
      end
    end
    
    # Generate header file
    hpp_content = ""
    keil_export = ""
    keil_incbin = ""

    avi2bin.each do |avi,bin|
      name = File.basename(bin).gsub('.', '_').gsub('/', '_')
      length = File.size(avi);

      symbol = "video_#{name}"
      start_symbol = "#{symbol}_start"
      length_symbol = "#{symbol}_length"

      hpp_content += "const uint32_t #{length_symbol} = #{length};\n"
      hpp_content += "#ifdef SIMULATOR\n"
      hpp_content += "extern const uint8_t* #{start_symbol};\n"
      hpp_content += "#else\n"
      hpp_content += "extern const uint8_t #{start_symbol}[];\n"
      hpp_content += "#endif\n\n"

      keil_export += "\tEXPORT\t#{start_symbol}\n"
      keil_incbin += "\n#{start_symbol}\n\tINCBIN\t#{touchgfx_folder_path}/#{bin}\n"
    end

    hpp_file = File.join(out_dir, 'include', 'videos', 'VideoDatabase.hpp')
    write_file(hpp_file,
               "// Generated by videoconvert. Please, do not edit!\n\n"+
               "#ifndef TOUCHGFX_VIDEODATABASE_HPP\n"+
               "#define TOUCHGFX_VIDEODATABASE_HPP\n"+
               "\n#include <touchgfx/hal/Types.hpp>\n\n"+
               hpp_content+
               "#endif // TOUCHGFX_VIDEODATABASE_HPP\n")

    keil_path = [out_dir, 'src', 'keil', 'Videos.s']
    keil_file = File.join(keil_path)
    # Only generate Videos.s if there are video files AND we are using Keil:
    if avi2bin.empty? || !toolchain_is_keil
      # Try to remove existing Videos.s file and folder structure (if it is empty)
      begin
        FileUtils.rm(keil_file) if File.exist?(keil_file)
        keil_folder = File.join(keil_path[0...-1])
        FileUtils.rmdir(keil_folder) if File.directory?(keil_folder)
        src_folder = File.join(keil_path[0...-2])
        FileUtils.rmdir(File.join(keil_path[0...-2])) if File.directory?(src_folder);
      rescue
      end
    else
      # There are videos and we are on Keil. Generate Videos.s
      write_file(keil_file,
                 "; Generated by videoconvert. Please, do not edit!\n\n"+
                 "\tAREA\tExtFlashSection, DATA, READONLY\n\n"+
                 keil_export+
                 keil_incbin+
                 "\n\tEND\n")
    end
  end
end
