# frozen_string_literal: true

# Redmine - project management software
# Copyright (C) 2006-2023  Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

module Redmine
  class PluginPath
    attr_reader :assets_dir, :initializer

    def initialize(dir)
      @dir = dir
      @assets_dir = File.join dir, 'assets'
      @initializer = File.join dir, 'init.rb'
    end

    def run_initializer
      load initializer if has_initializer?
    end

    def to_s
      @dir
    end

    def mirror_assets
      return unless has_assets_dir?

      destination = File.join(PluginLoader.public_directory, File.basename(@dir))

      source_files = Dir["#{assets_dir}/**/*"]
      source_dirs = source_files.select { |d| File.directory?(d)}
      source_files -= source_dirs
      unless source_files.empty?
        base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(assets_dir, ''))
        begin
          FileUtils.mkdir_p(base_target_dir)
        rescue => e
          raise "Could not create directory #{base_target_dir}: " + e.message
        end
      end

      source_dirs.each do |dir|
        # strip down these paths so we have simple, relative paths we can
        # add to the destination
        target_dir = File.join(destination, dir.gsub(assets_dir, ''))
        begin
          FileUtils.mkdir_p(target_dir)
        rescue => e
          raise "Could not create directory #{target_dir}: " + e.message
        end
      end
      source_files.each do |file|
        target = File.join(destination, file.gsub(assets_dir, ''))
        unless File.exist?(target) && FileUtils.identical?(file, target)
          FileUtils.cp(file, target)
        end
      rescue => e
        raise "Could not copy #{file} to #{target}: " + e.message
      end
    end

    def has_assets_dir?
      File.directory?(@assets_dir)
    end

    def has_initializer?
      File.file?(@initializer)
    end
  end

  class PluginLoader
    # Absolute path to the directory where plugins are located
    cattr_accessor :directory
    self.directory = Rails.root.join('plugins')

    # Absolute path to the plublic directory where plugins assets are copied
    cattr_accessor :public_directory
    self.public_directory = Rails.root.join('public/plugin_assets')

    def self.create_assets_reloader
      plugin_assets_dirs = {}
      directories.each do |dir|
        plugin_assets_dirs[dir.assets_dir] = ['*']
      end
      ActiveSupport::FileUpdateChecker.new([], plugin_assets_dirs) do
        mirror_assets
      end
    end

    def self.load
      setup
      add_autoload_paths

      Rails.application.config.to_prepare do
        PluginLoader.directories.each(&:run_initializer)

        Redmine::Hook.call_hook :after_plugins_loaded
      end
    end

    def self.setup
      @plugin_directories = []

      Dir.glob(File.join(directory, '*')).sort.each do |directory|
        next unless File.directory?(directory)

        @plugin_directories << PluginPath.new(directory)
      end
    end

    def self.add_autoload_paths
      directories.each do |directory|
        # Add the plugin directories to rails autoload paths
        engine_cfg = Rails::Engine::Configuration.new(directory.to_s)
        engine_cfg.paths.add 'lib', eager_load: true
        engine_cfg.eager_load_paths.each do |dir|
          Rails.autoloaders.main.push_dir dir
          Rails.application.config.watchable_dirs[dir] = [:rb]
        end
      end
    end

    def self.directories
      @plugin_directories
    end

    def self.mirror_assets(name=nil)
      if name.present?
        directories.find{|d| d.to_s == File.join(directory, name)}.mirror_assets
      else
        directories.each(&:mirror_assets)
      end
    end
  end
end
