# lib/convenience_functions.rb
# Extracted bits of crew-specific code that we use frequently enough that it makes sense to split them out to here.
require 'json'
require_relative 'color'
require_relative 'const'
require_relative 'crewlog'
require_relative 'downloader'

# Reimplementation of .blank? method from ActiveSupport
class NilClass; def blank? = true;         end
class Numeric;  def blank? = false;        end
class Proc;     def blank? = false;        end
class Array;    def blank? = empty?;       end
class Hash;     def blank? = empty?;       end
class Symbol;   def blank? = empty?;       end
class String;   def blank? = strip.empty?; end

class ConvenienceFunctions
  def self.determine_conflicts(pkg_name, filelist = File.join(CREW_META_PATH, "#{pkg_name}.filelist"), exclude_suffix = nil, verbose: false)
    conflicts       = {}
    target_filelist = File.readlines(filelist, chomp: true).grep(/^(?!#)/)

    puts 'Checking for conflicts with files from installed packages...'.orange if verbose

    Dir[File.join(CREW_META_PATH, '*.filelist')].each do |filelist|
      filelist_name = File.basename(filelist, '.filelist')

      # skip filelist belongs to the same package/explicitly excluded
      next if pkg_name == filelist_name || (exclude_suffix && filelist_name.end_with?(exclude_suffix))

      # find out identical file paths with intersection
      conflict = (target_filelist & File.readlines(filelist, chomp: true)).reject(&:empty?)
      conflicts[filelist_name] = conflict if conflict.any?
    end

    return conflicts
  end

  def self.get_package_disk_size(filelist)
    filelist.sum do |filename|
      # Skip calculating the filesize if the file doesn't exist.
      next 0 unless File.file?(filename)

      # Ignore symlinks to prevent duplicating calculation.
      next 0 if File.symlink?(filename)

      # Add the size of the file to the total size.
      File.size(filename)
    end
  end

  def self.load_symbolized_json
    unless File.file?(File.join(CREW_CONFIG_PATH, 'device.json'))
      puts 'Creating device.json.'
      device = {
        'architecture' => ARCH,
        'installed_packages' => []
      }
      FileUtils.mkdir_p CREW_CONFIG_PATH
      File.write(File.join(CREW_CONFIG_PATH, 'device.json'), JSON[device])
    end
    return JSON.load_file(File.join(CREW_CONFIG_PATH, 'device.json'), symbolize_names: true).transform_values! { |val| val.is_a?(String) ? val.to_sym : val }
  end

  def self.read_filelist(path, always_calcuate_from_disk: false)
    filelist = File.readlines(path, chomp: true)

    if filelist.first&.start_with?('# Total size') && !always_calcuate_from_disk
      total_size, *contents = filelist
      return [total_size[/Total size: (\d+)/, 1].to_i, contents]
    else
      return [get_package_disk_size(filelist), filelist]
    end
  end

  def self.save_json(json_object)
    crewlog 'Saving device.json...'
    begin
      File.write File.join(CREW_CONFIG_PATH, 'device.json.tmp'), JSON.pretty_generate(JSON.parse(json_object.to_json))
    rescue StandardError => e
      puts "Error writing updated packages json file!\n#{e.message}".lightred
      abort
    end

    # Copy over original if the write to the tmp file succeeds.
    FileUtils.cp("#{CREW_CONFIG_PATH}/device.json.tmp", File.join(CREW_CONFIG_PATH, 'device.json')) && FileUtils.rm("#{CREW_CONFIG_PATH}/device.json.tmp")
  end

  def self.libtoolize(library, lib_pkg_name = nil, install_dest = nil)
    install_dest = false if install_dest.nil?
    lib_pkg_name = library if lib_pkg_name.nil?
    libname = library.to_s.start_with?('lib') ? library.downcase : "lib#{library.downcase}"
    puts "Generating libtool file for #{lib_pkg_name}".orange
    puts "grep \"#{CREW_LIB_PREFIX}/#{libname}.so\\\|#{CREW_DEST_LIB_PREFIX}/#{libname}-*.so\" #{CREW_META_PATH}/#{lib_pkg_name}.filelist" if CREW_VERBOSE
    libnames = `grep "#{CREW_LIB_PREFIX}/#{libname}.so\\\|#{CREW_DEST_LIB_PREFIX}/#{libname}-*.so*" #{CREW_META_PATH}/#{lib_pkg_name}.filelist`.chomp.split(/$/).map(&:strip)
    libnames.each do |s|
      s.gsub!("#{CREW_LIB_PREFIX}/", '')
    end
    return if libnames.empty?
    dlname = libnames.grep(/.so./).first
    libname = dlname.gsub(/.so.\d+/, '')
    longest_libname = libnames.max_by(&:length)
    libvars = longest_libname.rpartition('.so.')[2].split('.')

    libtool_file = <<~LIBTOOLEOF
      # #{libname}.la - a libtool library file
      # Generated by libtool (GNU libtool) (Created by Chromebrew)
      #
      # Please DO NOT delete this file!
      # It is necessary for linking the library.

      # The name that we can dlopen(3).
      dlname='#{dlname}'

      # Names of this library.
      library_names='#{libnames.reverse.join(' ')}'

      # The name of the static archive.
      old_library='#{libname}.a'

      # Linker flags that cannot go in dependency_libs.
      inherited_linker_flags=''

      # Libraries that this one depends upon.
      dependency_libs=''

      # Names of additional weak libraries provided by this library
      weak_library_names=''

      # Version information for #{library}.
      current=#{libvars[1]}
      age=#{libvars[1]}
      revision=#{libvars[2]}

      # Is this an already installed library?
      installed=yes

      # Should we warn about portability when linking against -modules?
      shouldnotlink=no

      # Files to dlopen/dlpreopen
      dlopen=''
      dlpreopen=''

      # Directory that this library needs to be installed in:
      libdir='#{CREW_LIB_PREFIX}'
    LIBTOOLEOF
    File.write("#{libname}.la", libtool_file)
    %W[#{CREW_LIB_PREFIX}/#{libname}.la #{"#{CREW_DEST_LIB_PREFIX}/#{libname}.la" if install_dest}].reject(&:blank?).each do |lib|
      FileUtils.install "#{libname}.la", lib, mode: 0o755
      puts "Generated #{lib}..."
    end
  end

  def self.patch(patch_array = [])
    return if patch_array.empty?
    patch_array.each do |patch_item|
      abort 'Patch array is not valid!'.lightred unless patch_item[0]
      abort 'Patch sha256sum does not exist!'.lightred unless patch_item[1]
      patch_file = File.basename(patch_item[0])
      puts "downloader #{patch_item[0]}, #{patch_item[1]}" if CREW_VERBOSE
      downloader patch_item[0], patch_item[1]
      puts "patch -Np1 -i #{patch_file}" if CREW_VERBOSE
      system "patch -Np1 -i #{patch_file}"
    end
  end

  def self.set_default_browser(browser_name, browser_binary)
    print "\nSet #{browser_name} as your default browser? [Y/n]: "
    case $stdin.gets.chomp.downcase
    when '', 'y', 'yes'
      Dir.chdir("#{CREW_PREFIX}/bin") do
        FileUtils.ln_sf browser_binary, 'x-www-browser'
      end
      puts "#{browser_name} is now your default browser.".lightgreen
    else
      puts 'No change has been made.'.orange
    end
    ExitMessage.add "\nType '#{browser_binary}' to get started.\n"
  end

  def self.unset_default_browser(browser_name, browser_binary)
    Dir.chdir("#{CREW_PREFIX}/bin") do
      if File.exist?('x-www-browser') && File.symlink?('x-www-browser') &&
         ["#{CREW_PREFIX}/share/#{browser_name.downcase}/#{browser_binary}",
          "#{CREW_PREFIX}/bin/#{browser_binary}"].include?(File.realpath('x-www-browser'))
        FileUtils.rm "#{CREW_PREFIX}/bin/x-www-browser"
      end
    end
  end
end
