# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with this
# work for additional information regarding copyright ownership.  The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.

module Buildr #:nodoc:
  module Generate #:nodoc:

    task 'generate' do

      header = "To use Buildr you need a buildfile. Do you want me to create one?"
      options = {}
      if Generate.has_eclipse_project?
        options["From eclipse .project files"] = Proc.new { Generate.from_eclipse(Dir.pwd).join("\n") }
      end
      if File.exist?('pom.xml')
        options["From Maven2 POM file"] = Proc.new { Generate.from_maven2_pom('pom.xml').join("\n") }
      end
      options["From directory structure"] = Proc.new { Generate.from_directory(Dir.pwd).join("\n") }
      options["Skip"]= nil
      script = Buildr::Console.present_menu(header, options)
      if script
        buildfile = File.expand_path(Buildr::Application::DEFAULT_BUILDFILES.first)
        File.open(buildfile, "w") { |file| file.write script }
        puts "Created #{buildfile}"
      end
    end

    class << self

      def compatibility_option(path)
        # compile.options.target = '1.5'
      end

      def get_project_natures(projectFile)
        return nil unless File.exists?(projectFile)
        File.open(projectFile) do |f|
          root = REXML::Document.new(f).root
          return nil if root == nil
          natures = root.elements.collect("natures/nature") { |n| n.text }
          return natures if natures
        end
        return nil
      end

      def get_build_property(path, propertyName)
        propertiesFile = File.join(path, 'build.properties')
        return nil unless File.exists?(propertiesFile)
        inhalt = Hash.from_java_properties(File.read(propertiesFile))
        binDef = inhalt[propertyName]
      end

      def has_eclipse_project?
        candidates = Dir.glob("**/.project", File::FNM_DOTMATCH)
        return false if candidates.size == 0
        candidates.find { |x| get_project_natures(x) }
        return candidates.size > 0
      end


      HEADER = "# Generated by Buildr #{Buildr::VERSION}, change to your liking\n\n"

      def getEclipseBuildfileHeader(path, name)
        x = <<-EOF
#{"require 'buildr/scala'\n" if Dir.glob(path + "/**/*.scala").size > 0}
#{"require 'buildr/groovy'\n" if Dir.glob(path + "/**/*.groovy").size > 0}
# Version number for this release
VERSION_NUMBER = "1.0.0"
# Group identifier for your projects
GROUP = "#{name}"
COPYRIGHT = ""

# Specify Maven 2.0 remote repositories here, like this:
repositories.remote << "https://repo1.maven.org/maven2"

desc "The #{name.capitalize} project"
define "#{name}" do

  project.version = VERSION_NUMBER
  project.group = GROUP
  manifest["Implementation-Vendor"] = COPYRIGHT
            EOF
        return x
      end

      def setLayout(source=nil, output = nil)
        script = ""
        if source
          source = source.sub(/\/$/, '') # remove trailing /
          script += <<-EOF
  layout[:source, :main, :java] = "#{source}"
  layout[:source, :main, :scala] = "#{source}"
EOF
        end
        if output
          output = output.sub(/\/$/, '') # remove trailing /
          script += <<-EOF
  layout[:target, :main] = "#{output}"
  layout[:target, :main, :java] = "#{output}"
  layout[:target, :main, :scala] = "#{output}"
EOF
        end
        return script
      end

      # tries to read as much information as needed at the moment from an existing Eclipse workspace
      # Here are some links to the relevant information
      # * http://help.eclipse.org/juno/index.jsp?topic=/org.eclipse.pde.doc.user/reference/pde_feature_generating_build.htm
      # * http://wiki.eclipse.org/FAQ_What_is_the_plug-in_manifest_file_%28plugin.xml%29%3F
      # * http://help.eclipse.org/juno/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/misc/bundle_manifest.html
      # * http://help.eclipse.org/juno/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/misc/plugin_manifest.html
      # * http://help.eclipse.org/juno/index.jsp?topic=/org.eclipse.pde.doc.user/tasks/pde_compilation_env.htm
      def from_eclipse(path = Dir.pwd, root = true)
        # We need two passes to be able to determine the dependencies correctly
        Dir.chdir(path) do
          name = File.basename(path)
          dot_projects = []
          mf = nil # avoid reloading manifest
          if root
            @@allProjects = Hash.new
            @@topDir = File.expand_path(Dir.pwd)
            script = HEADER.split("\n")
            script << "require 'buildr/ide/eclipse'"
            header = getEclipseBuildfileHeader(path, name)
            script += header.split("\n")
            script << "  # you may see hints about which jars are missing and should resolve them correctly"
            script << "  # dependencies  << 'junit should be commented out and replace by correct ARTIFACT definition. Eg"
            script << "  # dependencies  << 'junit:junit:jar:4.11'"
            script << setLayout('src', 'bin') # default values for eclipse
            dot_projects = Dir.glob('**/.project', File::FNM_DOTMATCH).find_all { |dot_project| get_project_natures(dot_project) }
            dot_projects.sort.each { |dot_project| from_eclipse(File.dirname(dot_project), false) } if dot_projects
          else
            # Skip fragments. Buildr cannot handle it without the help of buildr4osgi
            return [""] if File.exists?('fragment.xml')
            projectName = name
            version = ""
            mfName = File.join('META-INF', 'MANIFEST.MF')
            if File.exists?(mfName)
              mf = Packaging::Java::Manifest.parse(IO.readlines(mfName).join(''))
              if mf.main['Bundle-SymbolicName']
                projectName = mf.main['Bundle-SymbolicName'].split(';')[0]
                bundleVersion = mf.main['Bundle-Version']
                version = ", :version => \"#{bundleVersion}\"" unless "1.0.0".eql?(bundleVersion)
              end
            end
            # in the first run we just want to know that we exist
            unless @@allProjects[projectName]
              @@allProjects[projectName] = Dir.pwd
              return
            end
            base_dir = ""
            unless File.join(@@topDir, projectName).eql?(File.expand_path(Dir.pwd))
              base_dir = ", :base_dir => \"#{File.expand_path(Dir.pwd).sub(@@topDir+File::SEPARATOR, '')}\""
            end
            script = [%{define "#{projectName}"#{version}#{base_dir} do}]
          end
          natures = get_project_natures('.project')
          if natures && natures.index('org.eclipse.pde.PluginNature')
            script << "  package(:jar)"
          end
          script << "  dependencies ||= []"
          if mf && mf.main['Require-Bundle']
            mf.main['Require-Bundle'].split(',').each do
              |bundle|
              requiredName = bundle.split(';')[0]
              if @@allProjects.has_key?(requiredName)
                script << "  dependencies << projects(\"#{requiredName}\")"
              else
                script << "  # dependencies  << '#{requiredName}'"
              end
            end
          end
          script << "  compile.with dependencies # Add more classpath dependencies" if Dir.glob(File.join('src', '**', '*.java')).size > 0
          script << "  resources" if File.exist?("rsc")
          sourceProp = get_build_property('.', 'source..')
          outputProp = get_build_property('.', 'output..')
          if (sourceProp && !/src\/+/.match(sourceProp)) or (outputProp && !/bin\/+/.match(outputProp))
            setLayout(sourceProp, outputProp) # default values are overridden in this project
          end
          unless dot_projects.empty?
            script << ""
            dot_projects.sort.each do |dot_project|
              next if File.dirname(File.expand_path(dot_project)).eql?(File.expand_path(Dir.pwd))
              next unless get_project_natures(dot_project)
              script << from_eclipse(File.dirname(dot_project), false).flatten.map { |line| "  " + line } << ""
            end
          end
          script << "end\n\n"
          script.flatten
        end
      end

      def from_directory(path = Dir.pwd, root = true)
        Dir.chdir(path) do
          name = File.basename(path)
          if root
            script = HEADER.split("\n")
            header = getEclipseBuildfileHeader(path, name)
            script += header.split("\n")
          else
            script = [ %{define "#{name}" do} ]
          end
          script <<  "  compile.with # Add classpath dependencies" if File.exist?("src/main/java")
          script <<  "  resources" if File.exist?("src/main/resources")
          script <<  "  test.compile.with # Add classpath dependencies" if File.exist?("src/test/java")
          script <<  "  test.resources" if File.exist?("src/test/resources")
          if File.exist?("src/main/webapp")
            script <<  "  package(:war)"
          elsif File.exist?("src/main/java")
            script <<  "  package(:jar)"
          end
          dirs = FileList["*"].exclude("src", "target", "report").
            select { |file| File.directory?(file) && File.exist?(File.join(file, "src")) }
          unless dirs.empty?
            script << ""
            dirs.sort.each do |dir|
              script << from_directory(dir, false).flatten.map { |line| "  " + line } << ""
            end
          end
          script << "end"
          script.flatten
        end
      end

      def from_maven2_pom(path = 'pom.xml', root = true)
        pom = Buildr::POM.load(path)
        project = pom.project

        artifactId = project['artifactId'].first
        description = project['name'] || "The #{artifactId} project"
        project_name = File.basename(Dir.pwd)

        if root
          script = HEADER.split("\n")

          settings_file = ENV["M2_SETTINGS"] || File.join(ENV['HOME'], ".m2/settings.xml")
          settings = XmlSimple.xml_in(IO.read(settings_file)) if File.exists?(settings_file)

          if settings
            proxy = settings['proxies'].first['proxy'].find { |proxy|
              proxy["active"].nil? || proxy["active"].to_s =~ /true/
            } rescue nil

            if proxy
              url = %{#{proxy["protocol"].first}://#{proxy["host"].first}:#{proxy["port"].first}}
              exclude = proxy["nonProxyHosts"].to_s.gsub("|", ",") if proxy["nonProxyHosts"]
              script << "options.proxy.http = '#{url}'"
              script << "options.proxy.exclude << '#{exclude}'" if exclude
              script << ''
              # In addition, we need to use said proxies to download artifacts.
              Buildr.options.proxy.http = url
              Buildr.options.proxy.exclude << exclude if exclude
            end
          end

          repositories = project["repositories"].first["repository"].select { |repository|
            legacy = repository["layout"].to_s =~ /legacy/
            !legacy
          } rescue nil
          repositories = [{"name" => "Standard maven2 repository", "url" => "https://repo1.maven.org/maven2"}] if repositories.nil? || repositories.empty?
          repositories.each do |repository|
            name, url = repository["name"], repository["url"]
            script << "# #{name}"
            script << "repositories.remote << '#{url}'"
            # In addition we need to use said repositores to download artifacts.
            Buildr.repositories.remote << url.to_s
          end
          script << ""
        else
          script = []
        end

        script << "desc '#{description}'"
        script << "define '#{project_name}' do"

        groupId = project['groupId']
        script << "  project.group = '#{groupId}'" if groupId

        version = project['version']
        script << "  project.version = '#{version}'" if version

        #get plugins configurations
        plugins = project['build'].first['plugins'].first['plugin'] rescue {}
        if plugins
          compile_plugin = plugins.find{|pl| (pl['groupId'].nil? or pl['groupId'].first == 'org.apache.maven.plugins') and pl['artifactId'].first == 'maven-compiler-plugin'}
          if compile_plugin
            source = compile_plugin.first['configuration'].first['source'] rescue nil
            target = compile_plugin.first['configuration'].first['target'] rescue nil

            script << "  compile.options.source = '#{source}'" if source
            script << "  compile.options.target = '#{target}'" if target
          end
        end

        compile_dependencies = pom.dependencies
        dependencies = compile_dependencies.sort.map{|d| "'#{d}'"}.join(', ')
        script <<  "  compile.with #{dependencies}" unless dependencies.empty?

        test_dependencies = (pom.dependencies(['test']) - compile_dependencies).reject{|d| d =~ /^junit:junit:jar:/ }
        #check if we have testng
        use_testng = test_dependencies.find{|d| d =~ /^org.testng:testng:jar:/}
        if use_testng
          script <<  "  test.using :testng"
          test_dependencies = pom.dependencies(['test']).reject{|d| d =~ /^org.testng:testng:jar:/ }
        end

        test_dependencies = test_dependencies.sort.map{|d| "'#{d}'"}.join(', ')
        script <<  "  test.with #{test_dependencies}" unless test_dependencies.empty?

        packaging = project['packaging'] ? project['packaging'].first : 'jar'
        if %w(jar war).include?(packaging)
          script <<  "  package :#{packaging}, :id => '#{artifactId}'"
        end

        modules = project['modules'].first['module'] rescue nil
        if modules
          script << ""
          modules.each do |mod|
            script << from_maven2_pom(File.join(File.dirname(path), mod, 'pom.xml'), false).flatten.map { |line| "  " + line } << ""
          end
        end
        script << "end"
        script.flatten
      end

    end
  end
end
