require 'util'

module War
  
  class Packer
  
    attr_accessor :config
  
    def initialize(config = Configuration.instance)
      @config = config
    end
    
    def install(source, dest, mode=0644)
      # File.install is disabled because of a performance problem under JRuby,
      # without it the task may fail if the original gem files were not writable by the owner
      #File.install(source, dest, mode)
      FileUtils.copy(source, dest)
    end
    
    def copy_tree(files, target, strip_prefix='')
      files.each do |f|
        relative = f[strip_prefix.length, f.length]
        target_file = File.join(target, relative)
        if File.directory?(f)
          FileUtils.makedirs(target_file)
        elsif File.file?(f)
          WLog.debug "  Copying #{f} to #{relative}"
          FileUtils.makedirs(File.dirname(target_file))
          install(f, target_file)
        end
      end    
    end
    
    def compile_tree(dir, config)
      return unless config.compile_ruby
      # find the libraries
      classpath_files = Rake::FileList.new(File.join(config.staging, 'WEB-INF', 'lib', '*.jar'))
      classpath_files_array = classpath_files.to_a
      classpath_files_array.collect! {|f| File.expand_path(f) }
      classpath = classpath_files_array.join(config.os_path_separator)
      # compile the files
      os_dir = dir.gsub(File::SEPARATOR, config.os_separator)
      unless system("cd #{os_dir} && java -cp #{classpath} org.jruby.webapp.ASTSerializerMain")
        raise "Error: failed to preparse files in #{dir}, returned with error code #{$?}"    
      end
    end
    
    def jar(target_file, source_dir, files, compress=true)
      os_target_file = target_file.gsub(File::SEPARATOR, config.os_separator)
      os_source_dir = source_dir.gsub(File::SEPARATOR, config.os_separator)
      flags = '-cf'
      flags += '0' unless compress
      files_list = files.join(' ')
      unless system("jar #{flags} \"#{os_target_file}\" -C \"#{os_source_dir}\" #{files_list}")
        raise "Error: failed to create archive, error code #{$?}"
      end
    end
    
  end
  
  class FileLibPacker < Packer
    def add_files
      WLog.info("Packing needed files ...")

      staging = config.staging
      lib = File.join(staging, 'WEB-INF', 'lib')
      classes = File.join(staging, 'WEB-INF', 'classes')
      base = File.join(staging, 'WEB-INF')
      install(:lib, lib)
      install(:classes, classes)
      install(:base, base)
    end

    def install(dir_sym, dir)
      WLog.debug("assembling files in " + dir + " directory")
      FileUtils.makedirs(dir)
      WLog.debug("need to assemble #{config.files.select{|k,v| v[:directory] == dir_sym }.size} files")
      for name, file_info in config.files
        next unless file_info[:directory] == dir_sym
        WLog.debug("file to add " + name )
        target = File.join(dir, name)
        WLog.debug("should install to " + target)
        file = file_info[:location]
        if !File.exists?(target) || File.mtime(target) < File.mtime(file_info[:location])
          WLog.info "  adding file #{name}"
          if File.exists?(file)
            FileUtils.copy(file, target)
          else
            WLog.warn "file '#{file}' does not exist"
          end
        end
      end
    end
  end
    
  class JavaLibPacker < Packer
    
    def initialize(config)
      super(config)
    end
    
    def add_java_libraries
      WLog.info("Packing needed Java libraries ...")
      staging = config.staging
      lib = File.join(staging, 'WEB-INF', 'lib')
      WLog.debug("assembling files in " + lib + " directory")
      FileUtils.makedirs(lib)
      WLog.debug("need to assemble #{config.java_libraries.size} libraries")
      for library in config.java_libraries.values
        WLog.debug("library to assemble " + library.name )
        target = File.join(lib, library.file)
        WLog.debug("should install to " + target)
        unless File.exists?(target)
          WLog.info "  adding Java library #{library}"
          library.install(config, target)
        end
      end
    end
  end
  
  class RubyLibPacker < Packer

    def initialize (config)
      super(config)
    end
    
    def add_ruby_libraries
      # add the gems
      WLog.info("Packing needed Ruby gems ...")
      gem_home = File.join(config.staging, 'WEB-INF', 'gems')
      FileUtils.makedirs(gem_home)
      FileUtils.makedirs(File.join(gem_home, 'gems'))
      FileUtils.makedirs(File.join(gem_home, 'specifications'))
      for gem in config.gem_libraries
        copy_gem(gem[0], gem[1], gem_home)
      end
    end
    
    def copy_gem(name, match_version, target_gem_home)
      require 'rubygems'
      matched = Gem.source_index.search(name, match_version)
      raise "The #{name} gem is not installed" if matched.empty?
      gem = matched.last

      gem_target = File.join(target_gem_home, 'gems', gem.full_gem_path.split('/').last)

      unless File.exists?(gem_target)
        # package up the gem
        WLog.info "  adding Ruby gem #{gem.name} version #{gem.version}"
        # copy the specification
        install(gem.loaded_from, File.join(target_gem_home, 'specifications'), 0644)
        # copy the files
        FileUtils.makedirs(gem_target)
        gem_files = Rake::FileList.new(File.join(gem.full_gem_path, '**', '*'))
        copy_tree(gem_files, gem_target, gem.full_gem_path)
        # compile the .rb files to .rb.ast.ser
        compile_tree(File.join(gem_target, 'lib'), config)
      end
      
      # handle dependencies
      if config.add_gem_dependencies
        for gem_child in gem.dependencies
          #puts "  Gem #{gem.name} requires #{gem_child.name} "
          copy_gem(gem_child.name, gem_child.requirements_list, target_gem_home)
        end
      end
    end
    
  end
  
  class WebappPacker < Packer

    def initialize(config)
      super(config)
    end
    
    def create_war
      # we can't include a directory, but exclude it's files
      webapp_files = Rake::FileList.new
      webapp_files.include('*')
      webapp_files.exclude('*.war')
      webapp_files.exclude(/tmp$/)
      webapp_files.include(File.join('tmp', '*'))
      webapp_files.exclude(File.join('tmp', 'jetty'))
      config.excludes.each do |exclude|
        webapp_files.exclude(exclude)
      end

      jar(config.war_file, config.staging, webapp_files)
    end
    
    def add_webapp
      staging = config.staging
      WLog.info 'Packing web application ...'
      FileUtils.makedirs(staging)
      webapp_files = Rake::FileList.new(File.join('.', '**', '*'))
      webapp_files.exclude(staging)
      webapp_files.exclude('*.war')
      webapp_files.exclude(config.local_java_lib)
      webapp_files.exclude(File.join('public', '.ht*'))
      webapp_files.exclude(File.join('public', 'dispatch*'))
      webapp_files.exclude(File.join('log', '*.log'))
      webapp_files.exclude(File.join('tmp', 'cache', '*'))
      webapp_files.exclude(File.join('tmp', 'sessions', '*'))
      webapp_files.exclude(File.join('tmp', 'sockets', '*'))
      webapp_files.exclude(File.join('tmp', 'jetty'))
      config.excludes.each do |exclude|
        webapp_files.exclude(exclude)
      end
      copy_tree(webapp_files, staging)
      compile_tree(staging, config)
    end
    
  end  
end