require 'jar_dependencies'
require 'jars/maven_exec'

module Jars
  class Installer
    class Dependency
      attr_reader :path, :file, :gav, :scope, :type, :coord

      def self.new(line)
        super if line =~ /:jar:|:pom:/
      end

      def setup_type(line)
        if line.index(':pom:')
          @type = :pom
        elsif line.index(':jar:')
          @type = :jar
        end
      end
      private :setup_type

      def setup_scope(line)
        @scope =
          case line
          when /:provided:/
            :provided
          when /:test:/
            :test
          else
            :runtime
          end
      end
      private :setup_scope

      REG = /:jar:|:pom:|:test:|:compile:|:runtime:|:provided:|:system:/
      EMPTY = ''.freeze
      def initialize(line)
        setup_type(line)

        line.strip!
        @coord = line.sub(/:[^:]+:([A-Z]:\\)?[^:]+$/, EMPTY)
        first, second = @coord.split(/:#{type}:/)
        group_id, artifact_id = first.split(/:/)
        parts = group_id.split('.')
        parts << artifact_id
        parts << second.split(':')[-1]
        @file = line.slice(@coord.length, line.length).sub(REG, EMPTY).strip
        last = @file.reverse.index(/\\|\//)
        parts << line[-last..-1]
        @path = File.join(parts).strip

        setup_scope(line)

        @system = !line.index(':system:').nil?
        @gav = @coord.sub(REG, ':')
      end

      def system?
        @system
      end
    end

    def self.install_jars(write_require_file = false)
      new.install_jars(write_require_file)
    end

    def self.load_from_maven(file)
      result = []
      File.read(file).each_line do |line|
        dep = Dependency.new(line)
        result << dep if dep && dep.scope == :runtime
      end
      result
    end

    def self.write_require_file(require_filename)
      warn 'deprecated'
      if needs_to_write?(require_filename)
        FileUtils.mkdir_p(File.dirname(require_filename))
        f = File.open(require_filename, 'w')
        f.puts COMMENT
        f.puts "require 'jar_dependencies'"
        f.puts
        f
      end
    end

    def self.vendor_file(dir, dep)
      if !dep.system? && dep.type == :jar && dep.scope == :runtime
        vendored = File.join(dir, dep.path)
        FileUtils.mkdir_p(File.dirname(vendored))
        FileUtils.cp(dep.file, vendored)
      end
    end

    def self.write_dep(file, _dir, dep, _vendor)
      warn 'deprecated'
      print_require_jar(file, dep)
    end

    def self.print_require_jar(file, dep, fallback = false)
      return if dep.type != :jar || dep.scope != :runtime
      if dep.system?
        file.puts("require '#{dep.file}'") if file
      elsif dep.scope == :runtime
        if fallback
          file.puts("  require '#{dep.path}'") if file
        else
          file.puts("  require_jar '#{dep.gav.gsub(':', "', '")}'") if file
        end
      end
    end

    COMMENT = '# this is a generated file, to avoid over-writing it just delete this comment'.freeze
    def self.needs_to_write?(require_filename)
      require_filename && (!File.exist?(require_filename) || File.read(require_filename).match(COMMENT))
    end

    def self.install_deps(deps, dir, require_filename, vendor)
      warn 'deprecated'
      write_require_jars(deps, require_filename)
      vendor_jars(deps, dir) if dir && vendor
    end

    def self.write_require_jars(deps, require_filename)
      if needs_to_write?(require_filename)
        FileUtils.mkdir_p(File.dirname(require_filename))
        File.open(require_filename, 'w') do |f|
          f.puts COMMENT
          f.puts 'begin'
          f.puts "  require 'jar_dependencies'"
          f.puts 'rescue LoadError'
          deps.each do |dep|
            # do not use require_jar method
            print_require_jar(f, dep, true)
          end
          f.puts 'end'
          f.puts
          f.puts 'if defined? Jars'
          deps.each do |dep|
            print_require_jar(f, dep)
          end
          f.puts 'end'
          yield f if block_given?
        end
      end
    end

    def self.vendor_jars(deps, dir)
      deps.each do |dep|
        vendor_file(dir, dep)
      end
    end

    def initialize(spec = nil)
      @mvn = MavenExec.new(spec)
    end

    def spec
      @mvn.spec
    end

    def vendor_jars(write_require_file = true, vendor_dir = nil)
      return unless has_jars?
      if Jars.to_prop(Jars::VENDOR) == 'false'
        vendor_dir = nil
      else
        vendor_dir ||= spec.require_path
      end
      do_install(vendor_dir, write_require_file)
    end

    def self.vendor_jars!(vendor_dir = nil)
      new.vendor_jars!(true, vendor_dir)
    end

    def vendor_jars!(write_require_file = true, vendor_dir = nil)
      vendor_dir ||= spec.require_path
      do_install(vendor_dir, write_require_file)
    end

    def install_jars(write_require_file = true)
      return unless has_jars?
      do_install(nil, write_require_file)
    end

    def ruby_maven_install_options=(options)
      @mvn.ruby_maven_install_options = options
    end

    def has_jars?
      # first look if there are any requirements in the spec
      # and then if gem depends on jar-dependencies for runtime.
      # only then install the jars declared in the requirements
      result = (spec = self.spec) && !spec.requirements.empty? &&
               spec.dependencies.detect { |d| d.name == 'jar-dependencies' && d.type == :runtime } != nil
      if result && spec.platform.to_s != 'java'
        Jars.warn "\njar dependencies found on non-java platform gem - do not install jars\n"
        false
      else
        result
      end
    end
    alias jars? has_jars?

    private

    def do_install(vendor_dir, write_require_file)
      if !spec.require_paths.include?(vendor_dir) && vendor_dir
        raise "vendor dir #{vendor_dir} not in require_paths of gemspec #{spec.require_paths}"
      end
      target_dir = File.join(@mvn.basedir, vendor_dir || spec.require_path)
      jars_file = File.join(target_dir, "#{spec.name}_jars.rb")

      # write out new jars_file it write_require_file is true or
      # check timestamps:
      # do not generate file if specfile is older then the generated file
      if !write_require_file &&
         File.exist?(jars_file) &&
         File.mtime(@mvn.specfile) < File.mtime(jars_file)
        # leave jars_file as is
        jars_file = nil
      end
      deps = install_dependencies
      self.class.write_require_jars(deps, jars_file)
      self.class.vendor_jars(deps, target_dir) if vendor_dir
    end

    def install_dependencies
      deps = File.join(@mvn.basedir, 'deps.lst')

      puts "  jar dependencies for #{spec.spec_name} . . ." unless Jars.quiet?
      @mvn.resolve_dependencies_list(deps)

      self.class.load_from_maven(deps)
    ensure
      FileUtils.rm_f(deps) if deps
    end
  end
  # to stay backward compatible
  JarInstaller = Installer unless defined? JarInstaller
end
