# frozen_string_literal: true

# Copyright (c) 2023-2024 [Ribose Inc](https://www.ribose.com).
# All rights reserved.
# This file is a part of tebako
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

require "etc"
require "fileutils"
require "pathname"
require "rbconfig"

require_relative "codegen"
require_relative "error"
require_relative "ruby_version"
require_relative "version"

# Tebako - an executable packager
# Command-line interface methods
module Tebako
  # Cli helpers
  class OptionsManager # rubocop:disable Metrics/ClassLength
    def initialize(options)
      @options = options
      @rv = Tebako::RubyVersion.new(@options["Ruby"])
      @ruby_ver, @ruby_hash = @rv.extend_ruby_version
    end

    attr_reader :ruby_ver, :rv

    def b_env
      u_flags = if RbConfig::CONFIG["host_os"] =~ /darwin/
                  "-DTARGET_OS_SIMULATOR=0 -DTARGET_OS_IPHONE=0  #{ENV.fetch("CXXFLAGS", nil)}"
                else
                  ENV.fetch("CXXFLAGS", nil)
                end
      @b_env ||= { "CXXFLAGS" => u_flags }
    end

    def cfg_options
      ## {v_parts[3]} may be something like rc1 that won't work with CMake
      v_parts = Tebako::VERSION.split(".")
      # Cannot use 'xxx' as parameters because it does not work in Windows shells
      # So we have to use \"xxx\"
      @cfg_options ||=
        "-DCMAKE_BUILD_TYPE=Release -DRUBY_VER:STRING=\"#{@ruby_ver}\" -DRUBY_HASH:STRING=\"#{@ruby_hash}\" " \
        "-DDEPS:STRING=\"#{deps}\" -G \"#{m_files}\" -B \"#{output_folder}\" -S \"#{source}\" " \
        "#{remove_glibc_private} -DTEBAKO_VERSION:STRING=\"#{v_parts[0]}.#{v_parts[1]}.#{v_parts[2]}\""
    end

    def cwd
      f_cwd = @options["cwd"]&.gsub("\\", "/")
      @cwd ||= f_cwd
    end

    # DATA_BIN_DIR folder is used to create packaged filesystem
    # set(DATA_BIN_DIR  ${CMAKE_CURRENT_BINARY_DIR}/p)
    def data_bin_dir
      @data_bin_dir ||= File.join(output_folder, "p")
    end

    # DATA_BIN_FILE is packaged filesystem itself
    # set(DATA_BIN_FILE ${DATA_BIN_DIR}/fs.bin)
    def data_bin_file
      @data_bin_file ||= File.join(data_bin_dir, "fs.bin")
    end

    # DATA_PRE_DIR folder is used to build gems  that need to be packaged
    # set(DATA_PRE_DIR  ${CMAKE_CURRENT_BINARY_DIR}/r)
    def data_pre_dir
      @data_pre_dir ||= File.join(output_folder, "r")
    end

    # DATA_SRC_DIR folder is used to collect all files that need to be packaged
    # set(DATA_SRC_DIR  ${CMAKE_CURRENT_BINARY_DIR}/s)
    def data_src_dir
      @data_src_dir ||= File.join(output_folder, "s")
    end

    def deps
      @deps ||= File.join(prefix, "deps")
    end

    def deps_bin_dir
      @deps_bin_dir ||= File.join(deps, "bin")
    end

    def fs_current
      fs_current = Dir.pwd
      if RUBY_PLATFORM =~ /msys|mingw|cygwin/
        fs_current, cygpath_res = Open3.capture2e("cygpath", "-w", fs_current)
        Tebako.packaging_error(101) unless cygpath_res.success?
        fs_current.strip!
      end
      @fs_current ||= fs_current&.gsub("\\", "/")
    end

    def fs_entrance
      @fs_entrance ||= @options["entry-point"]&.gsub("\\", "/")
    end

    def handle_nil_prefix
      env_prefix = ENV.fetch("TEBAKO_PREFIX", nil)
      if env_prefix.nil?
        puts "No prefix specified, using ~/.tebako"
        File.expand_path("~/.tebako")
      else
        puts "Using TEBAKO_PREFIX environment variable as prefix"
        File.expand_path(env_prefix.gsub("\\", "/"))
      end
    end

    def l_level
      @l_level ||= if @options["log-level"].nil?
                     "error"
                   else
                     @options["log-level"]
                   end
    end

    def m_files
      # [TODO]
      # Ninja generates incorrect script for tebako press target -- gets lost in a chain custom targets
      # Using makefiles has negative performance impact so it needs to be fixed
      @m_files ||= case RUBY_PLATFORM
                   when /linux/, /darwin/
                     "Unix Makefiles"
                   when /msys|mingw|cygwin/
                     "MinGW Makefiles"
                   else
                     raise Tebako::Error.new("#{RUBY_PLATFORM} is not supported.", 112)
                   end
    end

    def output_folder
      @output_folder ||= File.join(prefix, "o")
    end

    def package
      package = if @options["output"].nil?
                  File.join(Dir.pwd, File.basename(fs_entrance, ".*"))
                else
                  @options["output"]&.gsub("\\", "/")
                end
      @package ||= if relative?(package)
                     File.join(fs_current, package)
                   else
                     package
                   end
    end

    def prefix
      @prefix ||= if @options["prefix"].nil?
                    handle_nil_prefix
                  elsif @options["prefix"] == "PWD"
                    Dir.pwd
                  else
                    File.expand_path(@options["prefix"]&.gsub("\\", "/"))
                  end
    end

    def press_announce
      cwd_announce = cwd.nil? ? "<Host current directory>" : cwd
      @press_announce ||= <<~ANN
        Running tebako press at #{prefix}
           Ruby version:              '#{@ruby_ver}'
           Project root:              '#{root}'
           Application entry point:   '#{fs_entrance}'
           Package file name:         '#{package}'
           Loging level:              '#{l_level}'
           Package working directory: '#{cwd_announce}'
      ANN
    end

    def press_options
      @press_options ||= "-DPCKG:STRING='#{package}' -DLOG_LEVEL:STRING='#{l_level}' " \
    end

    def relative?(path)
      Pathname.new(path).relative?
    end

    def remove_glibc_private
      @remove_glibc_private ||= if RUBY_PLATFORM.end_with?("linux") || RUBY_PLATFORM.end_with?("linux-gnu")
                                  "-DREMOVE_GLIBC_PRIVATE=#{@options["patchelf"] ? "ON" : "OFF"}"
                                else
                                  ""
                                end
    end

    def root
      f_root = @options["root"].nil? ? "" : @options["root"].gsub("\\", "/")
      @root ||= if relative?(f_root)
                  File.join(fs_current, f_root)
                else
                  File.join(f_root, "")
                end
    end

    def ruby_src_dir
      @ruby_src_dir ||= File.join(deps, "src", "_ruby_#{@ruby_ver}")
    end

    def source
      c_path = Pathname.new(__FILE__).realpath
      @source ||= File.expand_path("../../..", c_path)
    end

    def stash_dir
      @stash_dir ||= "#{stash_dir_all}_#{@ruby_ver}"
    end

    def stash_dir_all
      @stash_dir_all ||= File.join(deps, "stash")
    end
  end
end