# 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 IntellijIdea
    def self.new_document(value)
      REXML::Document.new(value, :attribute_quote => :quote)
    end

    # Abstract base class for IdeaModule and IdeaProject
    class IdeaFile
      DEFAULT_PREFIX = ''
      DEFAULT_SUFFIX = ''
      DEFAULT_LOCAL_REPOSITORY_ENV_OVERRIDE = 'MAVEN_REPOSITORY'

      attr_reader :buildr_project
      attr_writer :prefix
      attr_writer :suffix
      attr_writer :id
      attr_accessor :template
      attr_accessor :local_repository_env_override

      def initialize
        @local_repository_env_override = DEFAULT_LOCAL_REPOSITORY_ENV_OVERRIDE
      end

      def prefix
        @prefix ||= DEFAULT_PREFIX
      end

      def suffix
        @suffix ||= DEFAULT_SUFFIX
      end

      def filename
        buildr_project.path_to("#{name}.#{extension}")
      end

      def id
        @id ||= buildr_project.name.split(':').last
      end

      def add_component(name, attrs = {}, &xml)
        self.components << create_component(name, attrs, &xml)
      end

      def add_component_from_file(filename)
        self.components << lambda do
          raise "Unable to locate file #{filename} adding component to idea file" unless File.exist?(filename)
          Buildr::IntellijIdea.new_document(IO.read(filename)).root
        end
      end

      def add_component_from_artifact(artifact)
        self.components << lambda do
          a = Buildr.artifact(artifact)
          a.invoke
          Buildr::IntellijIdea.new_document(IO.read(a.to_s)).root
        end
      end

      # IDEA can not handle text content with indents so need to removing indenting
      # Can not pass true as third argument as the ruby library seems broken
      def write(f)
        document.write(f, -1, false, true)
      end

      def name
        "#{prefix}#{self.id}#{suffix}"
      end

      protected

      def relative(path)
        ::Buildr::Util.relative_path(File.expand_path(path.to_s), self.base_directory)
      end

      def base_directory
        buildr_project.path_to
      end

      def resolve_path_from_base(path, base_variable)
        m2repo = Buildr::Repositories.instance.local
        if path.to_s.index(m2repo) == 0 && !self.local_repository_env_override.nil?
          return path.sub(m2repo, "$#{self.local_repository_env_override}$")
        else
          begin
            return "#{base_variable}/#{relative(path)}"
          rescue ArgumentError
            # ArgumentError happens on windows when self.base_directory and path are on different drives
            return path
          end
        end
      end

      def file_path(path)
        "file://#{resolve_path(path)}"
      end

      def create_component(name, attrs = {})
        target = StringIO.new
        Builder::XmlMarkup.new(:target => target, :indent => 2).component({:name => name}.merge(attrs)) do |xml|
          yield xml if block_given?
        end
        Buildr::IntellijIdea.new_document(target.string).root
      end

      def components
        @components ||= []
      end

      def create_composite_component(name, attrs, components)
        return nil if components.empty?
        component = self.create_component(name, attrs)
        components.each do |element|
          element = element.call if element.is_a?(Proc)
          component.add_element element
        end
        component
      end

      def add_to_composite_component(components)
        components << lambda do
          target = StringIO.new
          yield Builder::XmlMarkup.new(:target => target, :indent => 2)
          Buildr::IntellijIdea.new_document(target.string).root
        end
      end

      def load_document(filename)
        Buildr::IntellijIdea.new_document(File.read(filename))
      end

      def document
        if File.exist?(self.filename)
          doc = load_document(self.filename)
        else
          doc = base_document
          inject_components(doc, self.initial_components)
        end
        if self.template
          template_doc = load_document(self.template)
          REXML::XPath.each(template_doc, '//component') do |element|
            inject_component(doc, element)
          end
        end
        inject_components(doc, self.default_components.compact + self.components)

        # Sort the components in the same order the idea sorts them
        sorted = doc.root.get_elements('//component').sort { |s1, s2| s1.attribute('name').value <=> s2.attribute('name').value }
        doc = base_document
        sorted.each do |element|
          doc.root.add_element element
        end

        doc
      end

      def inject_components(doc, components)
        components.each do |component|
          # execute deferred components
          component = component.call if Proc === component
          inject_component(doc, component) if component
        end
      end

      # replace overridden component (if any) with specified component
      def inject_component(doc, component)
        doc.root.delete_element("//component[@name='#{component.attributes['name']}']")
        doc.root.add_element component
      end
    end

    # IdeaModule represents an .iml file
    class IdeaModule < IdeaFile
      DEFAULT_TYPE = 'JAVA_MODULE'

      attr_accessor :type
      attr_accessor :group
      attr_reader :facets
      attr_writer :jdk_version

      def initialize
        super()
        @type = DEFAULT_TYPE
      end

      def buildr_project=(buildr_project)
        @id = nil
        @facets = []
        @skip_content = false
        @buildr_project = buildr_project
      end

      def jdk_version
        @jdk_version || buildr_project.compile.options.source || '1.7'
      end

      def extension
        'iml'
      end

      def annotation_paths
        @annotation_paths ||= [buildr_project._(:source, :main, :annotations)].select {|p| File.exist?(p)}
      end

      def main_source_directories
        @main_source_directories ||= [buildr_project.compile.sources].flatten.compact
      end

      def main_resource_directories
        @main_resource_directories ||= [buildr_project.resources.sources].flatten.compact
      end

      def main_generated_source_directories
        @main_generated_source_directories ||= []
      end

      def main_generated_resource_directories
        @main_generated_resource_directories ||= []
      end

      def test_source_directories
        @test_source_directories ||= [buildr_project.test.compile.sources].flatten.compact
      end

      def test_resource_directories
        @test_resource_directories ||= [buildr_project.test.resources.sources].flatten.compact
      end

      def test_generated_source_directories
        @test_generated_source_directories ||= []
      end

      def test_generated_resource_directories
        @test_generated_resource_directories ||= []
      end

      def excluded_directories
        @excluded_directories ||= [
          buildr_project.resources.target,
          buildr_project.test.resources.target,
          buildr_project.path_to(:target, :main),
          buildr_project.path_to(:target, :test),
          buildr_project.path_to(:reports)
        ].flatten.compact
      end

      attr_writer :main_output_dir

      def main_output_dir
        @main_output_dir ||= buildr_project._(:target, :main, :idea, :classes)
      end

      attr_writer :test_output_dir

      def test_output_dir
        @test_output_dir ||= buildr_project._(:target, :test, :idea, :classes)
      end

      def main_dependencies
        @main_dependencies ||= buildr_project.compile.dependencies.dup
      end

      def test_dependencies
        @test_dependencies ||= buildr_project.test.compile.dependencies.dup
      end

      def add_facet(name, type)
        add_to_composite_component(self.facets) do |xml|
          xml.facet(:name => name, :type => type) do |xml|
            yield xml if block_given?
          end
        end
      end

      def skip_content?
        !!@skip_content
      end

      def skip_content!
        @skip_content = true
      end

      def add_gwt_facet(modules = {}, options = {})
        name = options[:name] || 'GWT'
        detected_gwt_version = nil
        if options[:gwt_dev_artifact]
          a = Buildr.artifact(options[:gwt_dev_artifact])
          a.invoke
          detected_gwt_version = a.to_s
        end

        settings =
          {
            :webFacet => 'Web',
            :compilerMaxHeapSize => '512',
            :compilerParameters => '-draftCompile -localWorkers 2 -strict',
            :gwtScriptOutputStyle => 'PRETTY'
          }.merge(options[:settings] || {})

        buildr_project.compile.dependencies.each do |d|
          if d.to_s =~ /\/com\/google\/gwt\/gwt-dev\/(.*)\//
            detected_gwt_version = d.to_s
            break
          end
        end unless detected_gwt_version

        if detected_gwt_version
          settings[:gwtSdkUrl] = resolve_path(File.dirname(detected_gwt_version))
          settings[:gwtSdkType] = 'maven'
        else
          settings[:gwtSdkUrl] = 'file://$GWT_TOOLS$'
        end

        add_facet(name, 'gwt') do |f|
          f.configuration do |c|
            settings.each_pair do |k, v|
              c.setting :name => k.to_s, :value => v.to_s
            end
            c.packaging do |d|
              modules.each_pair do |k, v|
                d.module :name => k, :enabled => v
              end
            end
          end
        end
      end

      def add_web_facet(options = {})
        name = options[:name] || 'Web'
        default_webroots = {}
        default_webroots[buildr_project._(:source, :main, :webapp)] = '/' if File.exist?(buildr_project._(:source, :main, :webapp))
        buildr_project.assets.paths.each {|p| default_webroots[p] = '/' }
        webroots = options[:webroots] || default_webroots
        default_deployment_descriptors = []
        %w(web.xml sun-web.xml glassfish-web.xml jetty-web.xml geronimo-web.xml context.xml weblogic.xml jboss-deployment-structure.xml jboss-web.xml ibm-web-bnd.xml ibm-web-ext.xml ibm-web-ext-pme.xml).
          each do |descriptor|
          webroots.each_pair do |path, relative_url|
            next unless relative_url == '/'
            d = "#{path}/WEB-INF/#{descriptor}"
            default_deployment_descriptors << d if File.exist?(d)
          end
        end
        deployment_descriptors = options[:deployment_descriptors] || default_deployment_descriptors

        add_facet(name, 'web') do |f|
          f.configuration do |c|
            c.descriptors do |d|
              deployment_descriptors.each do |deployment_descriptor|
                d.deploymentDescriptor :name => File.basename(deployment_descriptor), :url => file_path(deployment_descriptor)
              end
            end
            c.webroots do |w|
              webroots.each_pair do |webroot, relative_url|
                w.root :url => file_path(webroot), :relative => relative_url
              end
            end
          end
          default_enable_jsf = webroots.keys.any?{|webroot| File.exist?("#{webroot}/WEB-INF/faces-config.xml")}
          enable_jsf = options[:enable_jsf].nil? ? default_enable_jsf : options[:enable_jsf]
          enable_jsf = false if buildr_project.root_project.ipr? && buildr_project.root_project.ipr.version >= '13'
          f.facet(:type => 'jsf', :name => 'JSF') do |jsf|
            jsf.configuration
          end if enable_jsf
        end
      end

      def add_jruby_facet(options = {})
        name = options[:name] || 'JRuby'

        ruby_version_file = buildr_project._('.ruby-version')
        default_jruby_version = File.exist?(ruby_version_file) ? "rbenv: #{IO.read(ruby_version_file).strip}" : 'jruby-1.6.7.2'
        jruby_version = options[:jruby_version] || default_jruby_version
        add_facet(name, 'JRUBY') do |f|
          f.configuration do |c|
            c.JRUBY_FACET_CONFIG_ID :NAME => 'JRUBY_SDK_NAME', :VALUE => jruby_version
            c.LOAD_PATH :number => '0'
            c.I18N_FOLDERS :number => '0'
          end
        end
      end

      def add_jpa_facet(options = {})
        name = options[:name] || 'JPA'

        source_roots = [buildr_project.iml.main_source_directories, buildr_project.compile.sources, buildr_project.resources.sources].flatten.compact
        default_deployment_descriptors = []
        ['orm.xml', 'persistence.xml'].
          each do |descriptor|
          source_roots.each do |path|
            d = "#{path}/META-INF/#{descriptor}"
            default_deployment_descriptors << d if File.exist?(d)
          end
        end
        deployment_descriptors = options[:deployment_descriptors] || default_deployment_descriptors

        factory_entry = options[:factory_entry] || buildr_project.name.to_s
        validation_enabled = options[:validation_enabled].nil? ? true : options[:validation_enabled]
        if options[:provider_enabled]
          provider = options[:provider_enabled]
        else
          provider = nil
          {'org.hibernate.ejb.HibernatePersistence' => 'Hibernate',
           'org.eclipse.persistence.jpa.PersistenceProvider' => 'EclipseLink'}.
            each_pair do |match, candidate_provider|
            deployment_descriptors.each do |descriptor|
              if File.exist?(descriptor) && /#{Regexp.escape(match)}/ =~ IO.read(descriptor)
                provider = candidate_provider
              end
            end
          end
        end

        add_facet(name, 'jpa') do |f|
          f.configuration do |c|
            if provider
              c.setting :name => 'validation-enabled', :value => validation_enabled
              c.setting :name => 'provider-name', :value => provider
            end
            c.tag!('datasource-mapping') do |ds|
              ds.tag!('factory-entry', :name => factory_entry)
            end
            deployment_descriptors.each do |descriptor|
              c.deploymentDescriptor :name => File.basename(descriptor), :url => file_path(descriptor)
            end
          end
        end
      end

      def add_ejb_facet(options = {})
        name = options[:name] || 'EJB'

        default_ejb_roots = [buildr_project.iml.main_source_directories, buildr_project.compile.sources, buildr_project.resources.sources].flatten.compact
        ejb_roots = options[:ejb_roots] || default_ejb_roots

        default_deployment_descriptors = []
        %w(ejb-jar.xml glassfish-ejb-jar.xml ibm-ejb-jar-bnd.xml ibm-ejb-jar-ext-pme.xml ibm-ejb-jar-ext.xml jboss.xml jbosscmp-jdbc.xml openejb-jar.xml sun-cmp-mapping.xml sun-ejb-jar.xml weblogic-cmp-rdbms-jar.xml weblogic-ejb-jar.xml).
          each do |descriptor|
          ejb_roots.each do |path|
            d = "#{path}/WEB-INF/#{descriptor}"
            default_deployment_descriptors << d if File.exist?(d)
            d = "#{path}/META-INF/#{descriptor}"
            default_deployment_descriptors << d if File.exist?(d)
          end
        end
        deployment_descriptors = options[:deployment_descriptors] || default_deployment_descriptors

        add_facet(name, 'ejb') do |facet|
          facet.configuration do |c|
            c.descriptors do |d|
              deployment_descriptors.each do |deployment_descriptor|
                d.deploymentDescriptor :name => File.basename(deployment_descriptor), :url => file_path(deployment_descriptor)
              end
            end
            c.ejbRoots do |e|
              ejb_roots.each do |ejb_root|
                e.root :url => file_path(ejb_root)
              end
            end
          end
        end
      end

      protected

      def main_dependency_details
        target_dir = buildr_project.compile.target.to_s
        main_dependencies.select { |d| d.to_s != target_dir }.collect do |d|
          dependency_path = d.to_s
          export = true
          source_path = nil
          annotations_path = nil
          if d.is_a?(Buildr::Artifact)
            source_spec = d.to_spec_hash.merge(:classifier => 'sources')
            source_path = Buildr.artifact(source_spec).to_s
            source_path = nil unless File.exist?(source_path)
          end
          if d.is_a?(Buildr::Artifact)
            annotations_spec = d.to_spec_hash.merge(:classifier => 'annotations')
            annotations_path = Buildr.artifact(annotations_spec).to_s
            annotations_path = nil unless File.exist?(annotations_path)
          end
          [dependency_path, export, source_path, annotations_path]
        end
      end

      def test_dependency_details
        main_dependencies_paths = main_dependencies.map(&:to_s)
        target_dir = buildr_project.compile.target.to_s
        test_dependencies.select { |d| d.to_s != target_dir }.collect do |d|
          dependency_path = d.to_s
          export = main_dependencies_paths.include?(dependency_path)
          source_path = nil
          annotations_path = nil
          if d.is_a?(Buildr::Artifact)
            source_spec = d.to_spec_hash.merge(:classifier => 'sources')
            source_path = Buildr.artifact(source_spec).to_s
            source_path = nil unless File.exist?(source_path)
          end
          if d.is_a?(Buildr::Artifact)
            annotations_spec = d.to_spec_hash.merge(:classifier => 'annotations')
            annotations_path = Buildr.artifact(annotations_spec).to_s
            annotations_path = nil unless File.exist?(annotations_path)
          end
          [dependency_path, export, source_path, annotations_path]
        end
      end

      def base_document
        target = StringIO.new
        Builder::XmlMarkup.new(:target => target).module(:version => '4', :relativePaths => 'true', :type => self.type)
        Buildr::IntellijIdea.new_document(target.string)
      end

      def initial_components
        []
      end

      def default_components
        [
          lambda { module_root_component },
          lambda { facet_component }
        ]
      end

      def facet_component
        create_composite_component('FacetManager', {}, self.facets)
      end

      def module_root_component
        options = { 'inherit-compiler-output' => 'false' }
        options['LANGUAGE_LEVEL'] = "JDK_#{jdk_version.gsub(/\./, '_')}" unless jdk_version == buildr_project.root_project.compile.options.source
        create_component('NewModuleRootManager', options) do |xml|
          generate_compile_output(xml)
          generate_content(xml) unless skip_content?
          generate_initial_order_entries(xml)
          project_dependencies = []

          # If a project dependency occurs as a main dependency then add it to the list
          # that are excluded from list of test modules
          self.main_dependency_details.each do |dependency_path, export, source_path|
            next unless export
            project_for_dependency = Buildr.projects.detect do |project|
              [project.packages, project.compile.target, project.resources.target, project.test.compile.target, project.test.resources.target].flatten.
                detect { |artifact| artifact.to_s == dependency_path }
            end
            project_dependencies << project_for_dependency if project_for_dependency
          end

          main_project_dependencies = project_dependencies.dup
          self.test_dependency_details.each do |dependency_path, export, source_path, annotations_path|
            next if export
            generate_lib(xml, dependency_path, export, source_path, annotations_path, project_dependencies)
          end

          test_project_dependencies = project_dependencies - main_project_dependencies
          self.main_dependency_details.each do |dependency_path, export, source_path, annotations_path|
            next unless export
            generate_lib(xml, dependency_path, export, source_path, annotations_path, test_project_dependencies)
          end

          xml.orderEntryProperties
        end
      end

      def generate_lib(xml, dependency_path, export, source_path, annotations_path, project_dependencies)
        project_for_dependency = Buildr.projects.detect do |project|
          [project.packages, project.compile.target, project.resources.target, project.test.compile.target, project.test.resources.target].flatten.
            detect { |artifact| artifact.to_s == dependency_path }
        end
        if project_for_dependency
          if project_for_dependency.iml? &&
            !project_dependencies.include?(project_for_dependency) &&
            project_for_dependency != self.buildr_project
            generate_project_dependency(xml, project_for_dependency.iml.name, export, !export)
          end
          project_dependencies << project_for_dependency
        else
          generate_module_lib(xml, url_for_path(dependency_path), export, (source_path ? url_for_path(source_path) : nil), (annotations_path ? url_for_path(annotations_path) : nil), !export)
        end
      end

      def jar_path(path)
        "jar://#{resolve_path(path)}!/"
      end

      def url_for_path(path)
        if path =~ /jar$/i
          jar_path(path)
        else
          file_path(path)
        end
      end

      def resolve_path(path)
        resolve_path_from_base(path, '$MODULE_DIR$')
      end

      def generate_compile_output(xml)
        xml.output(:url => file_path(self.main_output_dir.to_s))
        xml.tag!('output-test', :url => file_path(self.test_output_dir.to_s))
        xml.tag!('exclude-output')
        paths = self.annotation_paths
        unless paths.empty?
          xml.tag!('annotation-paths') do |xml|
            paths.each do |path|
              xml.root(:url=> file_path(path))
            end
          end
        end
      end

      def generate_content(xml)
        xml.content(:url => 'file://$MODULE_DIR$') do
          # Source folders
          [
            {:dirs => (self.main_source_directories.dup - self.main_generated_source_directories)},
            {:dirs => self.main_generated_source_directories, :generated => true},
            {:type => 'resource', :dirs => (self.main_resource_directories.dup - self.main_generated_resource_directories)},
            {:type => 'resource', :dirs => self.main_generated_resource_directories, :generated => true},
            {:test => true, :dirs => (self.test_source_directories - self.test_generated_source_directories)},
            {:test => true, :dirs => self.test_generated_source_directories, :generated => true},
            {:test => true, :type => 'resource', :dirs => (self.test_resource_directories - self.test_generated_resource_directories)},
            {:test => true, :type => 'resource', :dirs => self.test_generated_resource_directories, :generated => true},
          ].each do |content|
            content[:dirs].map { |dir| dir.to_s }.compact.sort.uniq.each do |dir|
              options = {}
              options[:url] = file_path(dir)
              options[:isTestSource] = (content[:test] ? 'true' : 'false') if content[:type] != 'resource'
              options[:type] = 'java-resource' if content[:type] == 'resource' && !content[:test]
              options[:type] = 'java-test-resource' if content[:type] == 'resource' && content[:test]
              options[:generated] = 'true' if content[:generated]
              xml.sourceFolder options
            end
          end

          # Exclude target directories
          self.net_excluded_directories.
            collect { |dir| file_path(dir) }.
            select { |dir| relative_dir_inside_dir?(dir) }.
            sort.each do |dir|
            xml.excludeFolder :url => dir
          end
        end
      end

      def relative_dir_inside_dir?(dir)
        !dir.include?('../')
      end

      def generate_initial_order_entries(xml)
        xml.orderEntry :type => 'sourceFolder', :forTests => 'false'
        xml.orderEntry :type => 'jdk', :jdkName => jdk_version, :jdkType => 'JavaSDK'
      end

      def generate_project_dependency(xml, other_project, export, test = false)
        attribs = {:type => 'module', 'module-name' => other_project}
        attribs[:exported] = '' if export
        attribs[:scope] = 'TEST' if test
        xml.orderEntry attribs
      end

      def generate_module_lib(xml, path, export, source_path, annotations_path, test = false)
        attribs = {:type => 'module-library'}
        attribs[:exported] = '' if export
        attribs[:scope] = 'TEST' if test
        xml.orderEntry attribs do
          xml.library do
            xml.ANNOTATIONS do
              xml.root :url => annotations_path
            end if annotations_path
            xml.CLASSES do
              xml.root :url => path
            end
            xml.JAVADOC
            xml.SOURCES do
              if source_path
                xml.root :url => source_path
              end
            end
          end
        end
      end

      # Don't exclude things that are subdirectories of other excluded things
      def net_excluded_directories
        net = []
        all = self.excluded_directories.map { |dir| buildr_project._(dir.to_s) }.sort_by { |d| d.size }
        all.each_with_index do |dir, i|
          unless all[0 ... i].find { |other| dir =~ /^#{other}/ }
            net << dir
          end
        end
        net
      end
    end

    # IdeaModule represents an .ipr file
    class IdeaProject < IdeaFile
      attr_accessor :extra_modules
      attr_accessor :artifacts
      attr_accessor :data_sources
      attr_accessor :configurations
      attr_writer :jdk_version
      attr_writer :version

      def initialize(buildr_project)
        super()
        @buildr_project = buildr_project
        @extra_modules = []
        @artifacts = []
        @data_sources = []
        @configurations = []
      end

      def version
        @version || '13'
      end

      def jdk_version
        @jdk_version ||= buildr_project.compile.options.source || '1.7'
      end

      def add_artifact(name, type, build_on_make = false)
        add_to_composite_component(self.artifacts) do |xml|
          xml.artifact(:name => name, :type => type, 'build-on-make' => build_on_make) do |xml|
            yield xml if block_given?
          end
        end
      end

      def add_configuration(name, type, factory_name = nil, default = false, options = {})
        add_to_composite_component(self.configurations) do |xml|
          params = options.dup
          params[:type] = type
          params[:factoryName] = factory_name if factory_name
          params[:name] = name unless default
          params[:default] = !!default
          xml.configuration(params) do
            yield xml if block_given?
          end
        end
      end

      def add_default_configuration(type, factory_name)
        add_configuration(nil, type, factory_name, true) do |xml|
          yield xml if block_given?
        end
      end

      def mssql_dialect_mapping
        sql_dialect_mappings(buildr_project.base_dir => 'TSQL')
      end

      def postgres_dialect_mapping
        sql_dialect_mappings(buildr_project.base_dir => 'PostgreSQL')
      end

      def sql_dialect_mappings(mappings)
        add_component('SqlDialectMappings') do |component|
          mappings.each_pair do |path, dialect|
            file_path = file_path(path).gsub(/\/.$/, '')
            component.file :url => file_path, :dialect => dialect
          end
        end
      end

      def add_postgres_data_source(name, options = {})
        if options[:url].nil? && options[:database]
         default_url = "jdbc:postgresql://#{(options[:host] || '127.0.0.1')}:#{(options[:port] || '5432')}/#{options[:database]}"
        end

        params = {
          :driver => 'org.postgresql.Driver',
          :url => default_url,
          :username => ENV['USER'],
          :dialect => 'PostgreSQL',
          :classpath => ['org.postgresql:postgresql:jar:9.2-1003-jdbc4']
        }.merge(options)
        add_data_source(name, params)
      end

      def add_sql_server_data_source(name, options = {})
        default_url = nil
        if options[:url].nil? && options[:database]
          default_url = "jdbc:jtds:sqlserver://#{(options[:host] || '127.0.0.1')}:#{(options[:port] || '1433')}/#{options[:database]}"
        end

        params = {
          :driver => 'net.sourceforge.jtds.jdbc.Driver',
          :url => default_url,
          :username => ENV['USER'],
          :dialect => 'TSQL',
          :classpath => ['net.sourceforge.jtds:jtds:jar:1.2.7']
        }.merge(options)

        if params[:url]
          if /jdbc\:jtds\:sqlserver\:\/\/[^:\\]+(\:\d+)?\/([^;]*)(\;.*)?/ =~ params[:url]
            database_name = $2
            params[:schema_pattern] = "#{database_name}.*"
            params[:default_schemas] = "#{database_name}.*"
          end
        end

        add_data_source(name, params)
      end

      def add_data_source(name, options = {})
        add_to_composite_component(self.data_sources) do |xml|
          data_source_options = {
            :source => 'LOCAL',
            :name => name,
            :uuid => Buildr::Util.uuid
          }
          classpath = options[:classpath] || []
          xml.tag!('data-source', data_source_options) do |xml|
            xml.tag!('synchronize', (options[:synchronize]||'true'))
            xml.tag!('jdbc-driver', options[:driver]) if options[:driver]
            xml.tag!('jdbc-url', options[:url]) if options[:url]
            xml.tag!('user-name', options[:username]) if options[:username]
            xml.tag!('user-password', encrypt(options[:password])) if options[:password]
            xml.tag!('schema-pattern', options[:schema_pattern]) if options[:schema_pattern]
            xml.tag!('default-schemas', options[:default_schemas]) if options[:default_schemas]
            xml.tag!('table-pattern', options[:table_pattern]) if options[:table_pattern]
            xml.tag!('default-dialect', options[:dialect]) if options[:dialect]

            xml.libraries do |xml|
              classpath.each do |classpath_element|
                a = Buildr.artifact(classpath_element)
                a.invoke
                xml.library do |xml|
                  xml.tag!('url', resolve_path(a.to_s))
                end
              end
            end if classpath.size > 0
          end
        end
      end

      def add_exploded_war_artifact(project, options = {})
        artifact_name = to_artifact_name(project, options)
        artifacts = options[:artifacts] || []

        add_artifact(artifact_name, 'exploded-war', build_on_make(options)) do |xml|
          dependencies = (options[:dependencies] || ([project] + project.compile.dependencies)).flatten
          libraries, projects = partition_dependencies(dependencies)

          emit_output_path(xml, artifact_name, options)
          xml.root :id => 'root' do
            xml.element :id => 'directory', :name => 'WEB-INF' do
              xml.element :id => 'directory', :name => 'classes' do
                artifact_content(xml, project, projects, options)
              end
              xml.element :id => 'directory', :name => 'lib' do
                emit_libraries(xml, libraries)
                emit_jar_artifacts(xml, artifacts)
              end
            end

            if options[:enable_war].nil? || options[:enable_war] || (options[:war_module_names] && options[:war_module_names].size > 0)
              module_names = options[:war_module_names] || [project.iml.name]
              module_names.each do |module_name|
                facet_name = options[:war_facet_name] || 'Web'
                xml.element :id => 'javaee-facet-resources', :facet => "#{module_name}/web/#{facet_name}"
              end
            end

            if options[:enable_gwt] || (options[:gwt_module_names] && options[:gwt_module_names].size > 0)
              module_names = options[:gwt_module_names] || [project.iml.name]
              module_names.each do |module_name|
                facet_name = options[:gwt_facet_name] || 'GWT'
                xml.element :id => 'gwt-compiler-output', :facet => "#{module_name}/gwt/#{facet_name}"
              end
            end
          end
        end
      end

      def add_exploded_ear_artifact(project, options ={})
        artifact_name = to_artifact_name(project, options)

        add_artifact(artifact_name, 'exploded-ear', build_on_make(options)) do |xml|
          dependencies = (options[:dependencies] || ([project] + project.compile.dependencies)).flatten
          libraries, projects = partition_dependencies(dependencies)

          emit_output_path(xml, artifact_name, options)
          xml.root :id => 'root' do
            emit_module_output(xml, projects)
            xml.element :id => 'directory', :name => 'lib' do
              emit_libraries(xml, libraries)
            end
          end
        end
      end

      def add_jar_artifact(project, options = {})
        artifact_name = to_artifact_name(project, options)

        dependencies = (options[:dependencies] || [project]).flatten
        libraries, projects = partition_dependencies(dependencies)
        raise "Unable to add non-project dependencies (#{libraries.inspect}) to jar artifact" if libraries.size > 0

        jar_name = "#{artifact_name}.jar"
        add_artifact(jar_name, 'jar', build_on_make(options)) do |xml|
          emit_output_path(xml, artifact_name, options)
          xml.root(:id => 'archive', :name => jar_name) do
            artifact_content(xml, project, projects, options)
          end
        end
      end

      def add_exploded_ejb_artifact(project, options = {})
        artifact_name = to_artifact_name(project, options)

        add_artifact(artifact_name, 'exploded-ejb', build_on_make(options)) do |xml|
          dependencies = (options[:dependencies] || [project]).flatten
          libraries, projects = partition_dependencies(dependencies)
          raise "Unable to add non-project dependencies (#{libraries.inspect}) to ejb artifact" if libraries.size > 0

          emit_output_path(xml, artifact_name, options)
          xml.root :id => 'root' do
            artifact_content(xml, project, projects, options)
          end
        end
      end

      def add_java_configuration(project, classname, options = {})
        args = options[:args] || ''
        dir = options[:dir] || 'file://$PROJECT_DIR$/'
        debug_port = options[:debug_port] || 2599
        module_name = options[:module_name] || project.iml.name
        jvm_args = options[:jvm_args] || ''
        name = options[:name] || classname

        add_to_composite_component(self.configurations) do |xml|
          xml.configuration(:name => name, :type => 'Application', :factoryName => 'Application', :default => !!options[:default]) do |xml|
            xml.extension(:name => 'coverage', :enabled => 'false', :merge => 'false', :sample_coverage => 'true', :runner => 'idea')
            xml.option(:name => 'MAIN_CLASS_NAME', :value => classname)
            xml.option(:name => 'VM_PARAMETERS', :value => jvm_args)
            xml.option(:name => 'PROGRAM_PARAMETERS', :value => args)
            xml.option(:name => 'WORKING_DIRECTORY', :value => dir)
            xml.option(:name => 'ALTERNATIVE_JRE_PATH_ENABLED', :value => 'false')
            xml.option(:name => 'ALTERNATIVE_JRE_PATH', :value => '')
            xml.option(:name => 'ENABLE_SWING_INSPECTOR', :value => 'false')
            xml.option(:name => 'ENV_VARIABLES')
            xml.option(:name => 'PASS_PARENT_ENVS', :value => 'true')
            xml.module(:name => module_name)
            xml.envs
            xml.RunnerSettings(:RunnerId => 'Debug') do |xml|
              xml.option(:name => 'DEBUG_PORT', :value => debug_port.to_s)
              xml.option(:name => 'TRANSPORT', :value => '0')
              xml.option(:name => 'LOCAL', :value => 'true')
            end
            xml.RunnerSettings(:RunnerId => 'Run')
            xml.ConfigurationWrapper(:RunnerId => 'Debug')
            xml.ConfigurationWrapper(:RunnerId => 'Run')
            xml.method
          end
        end
      end

      def add_ruby_script_configuration(project, script, options = {})
        args = options[:args] || ''
        path = ::Buildr::Util.relative_path(File.expand_path(script), project.base_dir)
        name = options[:name] || File.basename(script)
        dir = options[:dir] || "$MODULE_DIR$/#{path}"
        sdk = options[:sdk] || 'rbenv: ' + (IO.read(File.dirname(__FILE__) + '/../.ruby-version').trim rescue "jruby-#{RUBY_VERSION}")

        add_to_composite_component(self.configurations) do |xml|
          xml.configuration(:name => name, :type => 'RubyRunConfigurationType', :factoryName => 'Ruby', :default => !!options[:default]) do |xml|

            xml.module(:name => project.iml.name)
            xml.RUBY_RUN_CONFIG(:NAME => 'RUBY_ARGS', :VALUE => '-e STDOUT.sync=true;STDERR.sync=true;load($0=ARGV.shift)')
            xml.RUBY_RUN_CONFIG(:NAME => 'WORK DIR', :VALUE => dir)
            xml.RUBY_RUN_CONFIG(:NAME => 'SHOULD_USE_SDK', :VALUE => 'true')
            xml.RUBY_RUN_CONFIG(:NAME => 'ALTERN_SDK_NAME', :VALUE => sdk)
            xml.RUBY_RUN_CONFIG(:NAME => 'myPassParentEnvs', :VALUE => 'true')

            xml.envs
            xml.EXTENSION(:ID => 'BundlerRunConfigurationExtension', :bundleExecEnabled => 'false')
            xml.EXTENSION(:ID => 'JRubyRunConfigurationExtension')

            xml.RUBY_RUN_CONFIG(:NAME => 'SCRIPT_PATH', :VALUE => script)
            xml.RUBY_RUN_CONFIG(:NAME => 'SCRIPT_ARGS', :VALUE => args)
            xml.RunnerSettings(:RunnerId => 'RubyDebugRunner')
            xml.ConfigurationWrapper(:RunnerId => 'RubyDebugRunner')
          end
        end
      end

      def add_gwt_configuration(project, options = {})
        launch_page = options[:launch_page]
        name = options[:name] || (launch_page ? "Run #{launch_page}" : "Run #{project.name} DevMode")
        shell_parameters = options[:shell_parameters]
        vm_parameters = options[:vm_parameters] || '-Xmx512m'
        singleton = options[:singleton].nil? ? true : !!options[:singleton]
        super_dev = options[:super_dev].nil? ? true : !!options[:super_dev]
        gwt_module = options[:gwt_module]

        start_javascript_debugger = options[:start_javascript_debugger].nil? ? true : !!options[:start_javascript_debugger]

        add_configuration(name, 'GWT.ConfigurationType', 'GWT Configuration', false, :singleton => singleton) do |xml|
          xml.module(:name => options[:iml_name] || project.iml.name)

          xml.option(:name => 'VM_PARAMETERS', :value => vm_parameters)
          xml.option(:name => 'RUN_PAGE', :value => launch_page) if launch_page
          xml.option(:name => 'GWT_MODULE', :value => gwt_module) if gwt_module

          # noinspection RubySimplifyBooleanInspection
          xml.option(:name => 'OPEN_IN_BROWSER', :value => false) if options[:open_in_browser] == false
          xml.option(:name => 'START_JAVASCRIPT_DEBUGGER', :value => start_javascript_debugger)
          xml.option(:name => 'USE_SUPER_DEV_MODE', :value => super_dev)
          xml.option(:name => 'SHELL_PARAMETERS', :value => shell_parameters) if shell_parameters

          xml.RunnerSettings(:RunnerId => 'Debug') do
            xml.option(:name => 'DEBUG_PORT', :value => '')
            xml.option(:name => 'TRANSPORT', :value => 0)
            xml.option(:name => 'LOCAL', :value => true)
          end

          xml.RunnerSettings(:RunnerId => 'Run')
          xml.ConfigurationWrapper(:RunnerId => 'Run')
          xml.ConfigurationWrapper(:RunnerId => 'Debug')
          xml.method
        end
      end

      def add_glassfish_configuration(project, options = {})
        artifact_name = options[:name] || project.iml.id
        version = options[:version] || '4.1.0'
        server_name = options[:server_name] || "GlassFish #{version}"
        configuration_name = options[:configuration_name] || server_name
        domain_name = options[:domain] || project.iml.id
        domain_port = options[:port] || '9009'
        packaged = options[:packaged] || {}
        exploded = options[:exploded] || []

        add_to_composite_component(self.configurations) do |xml|
          xml.configuration(:name => configuration_name, :type => 'GlassfishConfiguration', :factoryName => 'Local', :default => false, :APPLICATION_SERVER_NAME => server_name) do |xml|
            xml.option(:name => 'OPEN_IN_BROWSER', :value => 'false')
            xml.option(:name => 'UPDATING_POLICY', :value => 'restart-server')

            xml.deployment do |deployment|
              packaged.each do |name, deployable|
                artifact = Buildr.artifact(deployable)
                artifact.invoke
                deployment.file(:path => resolve_path(artifact.to_s)) do |file|
                  file.settings do |settings|
                    settings.option(:name => 'contextRoot', :value => "/#{name}")
                    settings.option(:name => 'defaultContextRoot', :value => 'false')
                  end
                end
              end
              exploded.each do |deployable_name|
                deployment.artifact(:name => deployable_name) do |artifact|
                  artifact.settings
                end
              end
            end

            xml.tag! 'server-settings' do |server_settings|
              server_settings.option(:name => 'VIRTUAL_SERVER')
              server_settings.option(:name => 'DOMAIN', :value => domain_name.to_s)
              server_settings.option(:name => 'PRESERVE', :value => 'false')
              server_settings.option(:name => 'USERNAME', :value => 'admin')
              server_settings.option(:name => 'PASSWORD', :value => '')
            end

            xml.predefined_log_file(:id => 'GlassFish', :enabled => 'true')

            xml.extension(:name => 'coverage', :enabled => 'false', :merge => 'false', :sample_coverage => 'true', :runner => 'idea')

            xml.RunnerSettings(:RunnerId => 'Cover')

            add_glassfish_runner_settings(xml, 'Cover')
            add_glassfish_configuration_wrapper(xml, 'Cover')

            add_glassfish_runner_settings(xml, 'Debug', {
              :DEBUG_PORT => domain_port.to_s,
              :TRANSPORT => '0',
              :LOCAL => 'true',
            })
            add_glassfish_configuration_wrapper(xml, 'Debug')

            add_glassfish_runner_settings(xml, 'Run')
            add_glassfish_configuration_wrapper(xml, 'Run')

            xml.method do |method|
              method.option(:name => 'BuildArtifacts', :enabled => 'true') do |option|
                option.artifact(:name => artifact_name)
              end
            end
          end
        end
      end

      def add_glassfish_remote_configuration(project, options = {})
        artifact_name = options[:name] || project.iml.id
        version = options[:version] || '4.1.0'
        server_name = options[:server_name] || "GlassFish #{version}"
        configuration_name = options[:configuration_name] || "Remote #{server_name}"
        domain_port = options[:port] || '9009'
        packaged = options[:packaged] || {}
        exploded = options[:exploded] || []

        add_to_composite_component(self.configurations) do |xml|
          xml.configuration(:name => configuration_name, :type => 'GlassfishConfiguration', :factoryName => 'Remote', :default => false, :APPLICATION_SERVER_NAME => server_name) do |xml|
            xml.option(:name => 'LOCAL', :value => 'false')
            xml.option(:name => 'OPEN_IN_BROWSER', :value => 'false')
            xml.option(:name => 'UPDATING_POLICY', :value => 'hot-swap-classes')

            xml.deployment do |deployment|
              packaged.each do |name, deployable|
                artifact = Buildr.artifact(deployable)
                artifact.invoke
                deployment.file(:path => resolve_path(artifact.to_s)) do |file|
                  file.settings do |settings|
                    settings.option(:name => 'contextRoot', :value => "/#{name}")
                    settings.option(:name => 'defaultContextRoot', :value => 'false')
                  end
                end
              end
              exploded.each do |deployable_name|
                deployment.artifact(:name => deployable_name) do |artifact|
                  artifact.settings
                end
              end
            end

            xml.tag! 'server-settings' do |server_settings|
              server_settings.option(:name => 'VIRTUAL_SERVER')
              server_settings.data do |data|
                data.option(:name => 'adminServerHost', :value => '')
                data.option(:name => 'clusterName', :value => '')
                data.option(:name => 'stagingRemotePath', :value => '')
                data.option(:name => 'transportHostId')
                data.option(:name => 'transportStagingTarget') do |option|
                  option.TransportTarget do |tt|
                    tt.option(:name => 'id', :value => 'X')
                  end
                end
              end
            end

            xml.predefined_log_file(:id => 'GlassFish', :enabled => 'true')

            add_glassfish_runner_settings(xml, 'Debug', {
                                               :DEBUG_PORT => domain_port.to_s,
                                               :TRANSPORT => '0',
                                               :LOCAL => 'false',
                                             })
            add_glassfish_configuration_wrapper(xml, 'Debug')

            add_glassfish_runner_settings(xml, 'Run')
            add_glassfish_configuration_wrapper(xml, 'Run')

            xml.method do |method|
              method.option(:name => 'BuildArtifacts', :enabled => 'true') do |option|
                option.artifact(:name => artifact_name)
              end
            end
          end
        end
      end

      def add_testng_configuration(name, options = {})
        jvm_args = options[:jvm_args] || '-ea'
        module_name = options[:module] || ''
        dir = options[:dir]

        opts = {}
        opts[:folderName] = options[:folderName] if options[:folderName]
        add_configuration(name, 'TestNG', nil, false, opts) do |xml|
          xml.module(:name => module_name)
          xml.option(:name => 'SUITE_NAME', :value => '')
          xml.option(:name => 'PACKAGE_NAME', :value => options[:package_name] || '')
          xml.option(:name => 'MAIN_CLASS_NAME', :value => options[:class_name] || '')
          xml.option(:name => 'METHOD_NAME', :value => options[:method_name] || '')
          xml.option(:name => 'GROUP_NAME', :value => options[:group_name] || '')
          xml.option(:name => 'TEST_OBJECT', :value => (!options[:class_name].nil? ? 'CLASS' : 'PACKAGE'))
          xml.option(:name => 'VM_PARAMETERS', :value => jvm_args)
          xml.option(:name => 'PARAMETERS', :value => '-configfailurepolicy continue')
          xml.option(:name => 'WORKING_DIRECTORY', :value => dir) if dir
          xml.option(:name => 'OUTPUT_DIRECTORY', :value => '')
          xml.option(:name => 'PROPERTIES_FILE', :value => '')
          xml.properties
          xml.listeners
          xml.method(:v => '2') do
            xml.option(:name => 'Make', :enabled => 'true')
          end
        end
      end

      def add_default_testng_configuration(options = {})
        jvm_args = options[:jvm_args] || '-ea'
        dir = options[:dir] || '$PROJECT_DIR$'

        add_default_configuration('TestNG', 'TestNG') do |xml|
          xml.extension(:name => 'coverage', :enabled => 'false', :merge => 'false', :sample_coverage => 'true', :runner => 'idea')
          xml.module(:name => '')
          xml.option(:name => 'ALTERNATIVE_JRE_PATH_ENABLED', :value => 'false')
          xml.option(:name => 'ALTERNATIVE_JRE_PATH')
          xml.option(:name => 'SUITE_NAME')
          xml.option(:name => 'PACKAGE_NAME')
          xml.option(:name => 'MAIN_CLASS_NAME')
          xml.option(:name => 'METHOD_NAME')
          xml.option(:name => 'GROUP_NAME')
          xml.option(:name => 'TEST_OBJECT', :value => 'CLASS')
          xml.option(:name => 'VM_PARAMETERS', :value => jvm_args)
          xml.option(:name => 'PARAMETERS')
          xml.option(:name => 'WORKING_DIRECTORY', :value => dir)
          xml.option(:name => 'OUTPUT_DIRECTORY')
          xml.option(:name => 'ANNOTATION_TYPE')
          xml.option(:name => 'ENV_VARIABLES')
          xml.option(:name => 'PASS_PARENT_ENVS', :value => 'true')
          xml.option(:name => 'TEST_SEARCH_SCOPE') do |opt|
            opt.value(:defaultName => 'moduleWithDependencies')
          end
          xml.option(:name => 'USE_DEFAULT_REPORTERS', :value => 'false')
          xml.option(:name => 'PROPERTIES_FILE')
          xml.envs
          xml.properties
          xml.listeners
          xml.method
        end
      end

      protected

      def add_glassfish_runner_settings(xml, name, options = {})
        xml.RunnerSettings(:RunnerId => name.to_s) do |runner_settings|
          options.each do |key, value|
            runner_settings.option(:name => key.to_s, :value => value.to_s)
          end
        end
      end

      def add_glassfish_configuration_wrapper(xml, name)
        xml.ConfigurationWrapper(:VM_VAR => 'JAVA_OPTS', :RunnerId => name.to_s) do |configuration_wrapper|
          configuration_wrapper.option(:name => 'USE_ENV_VARIABLES', :value => 'true')
          configuration_wrapper.STARTUP do |startup|
            startup.option(:name => 'USE_DEFAULT', :value => 'true')
            startup.option(:name => 'SCRIPT', :value => '')
            startup.option(:name => 'VM_PARAMETERS', :value => '')
            startup.option(:name => 'PROGRAM_PARAMETERS', :value => '')
          end
          configuration_wrapper.SHUTDOWN do |shutdown|
            shutdown.option(:name => 'USE_DEFAULT', :value => 'true')
            shutdown.option(:name => 'SCRIPT', :value => '')
            shutdown.option(:name => 'VM_PARAMETERS', :value => '')
            shutdown.option(:name => 'PROGRAM_PARAMETERS', :value => '')
          end
        end
      end

      def artifact_content(xml, project, projects, options)
        emit_module_output(xml, projects)
        emit_jpa_descriptors(xml, project, options)
        emit_ejb_descriptors(xml, project, options)
      end

      def extension
        'ipr'
      end

      def base_document
        target = StringIO.new
        Builder::XmlMarkup.new(:target => target).project(:version => '4')
        Buildr::IntellijIdea.new_document(target.string)
      end

      def default_components
        [
          lambda { modules_component },
          vcs_component,
          artifacts_component,
          lambda { data_sources_component },
          configurations_component,
          lambda { framework_detection_exclusion_component }
        ]
      end

      def framework_detection_exclusion_component
        create_component('FrameworkDetectionExcludesConfiguration') do |xml|
          xml.file :url => file_path(buildr_project._(:artifacts))
        end
      end

      def initial_components
        [
          lambda { project_root_manager_component },
          lambda { project_details_component }
        ]
      end

      def project_root_manager_component
        attribs = {}
        attribs['version'] = '2'
        attribs['languageLevel'] = "JDK_#{self.jdk_version.gsub('.', '_')}"
        attribs['assert-keyword'] = 'true'
        attribs['jdk-15'] = (jdk_version >= '1.5').to_s
        attribs['project-jdk-name'] = self.jdk_version
        attribs['project-jdk-type'] = 'JavaSDK'
        create_component('ProjectRootManager', attribs) do |xml|
          xml.output('url' => file_path(buildr_project._(:target, :idea, :project_out)))
        end
      end

      def project_details_component
        create_component('ProjectDetails') do |xml|
          xml.option('name' => 'projectName', 'value' => self.name)
        end
      end

      def modules_component
        create_component('ProjectModuleManager') do |xml|
          xml.modules do
            buildr_project.projects.select { |subp| subp.iml? }.each do |subproject|
              module_path = subproject.base_dir.gsub(/^#{buildr_project.base_dir}\//, '')
              path = "#{module_path}/#{subproject.iml.name}.iml"
              attribs = {:fileurl => "file://$PROJECT_DIR$/#{path}", :filepath => "$PROJECT_DIR$/#{path}"}
              if subproject.iml.group == true
                attribs[:group] = subproject.parent.name.gsub(':', '/')
              elsif !subproject.iml.group.nil?
                attribs[:group] = subproject.iml.group.to_s
              end
              xml.module attribs
            end
            self.extra_modules.each do |iml_file|
              xml.module :fileurl => "file://$PROJECT_DIR$/#{iml_file}",
                         :filepath => "$PROJECT_DIR$/#{iml_file}"
            end
            if buildr_project.iml?
              xml.module :fileurl => "file://$PROJECT_DIR$/#{buildr_project.iml.name}.iml",
                         :filepath => "$PROJECT_DIR$/#{buildr_project.iml.name}.iml"
            end
          end
        end
      end

      def vcs_component
        project_directories = buildr_project.projects.select { |p| p.iml? }.collect { |p| p.base_dir }
        project_directories << buildr_project.base_dir
        # Guess the iml file is in the same dir as base dir
        project_directories += self.extra_modules.collect { |p| File.dirname(p) }

        project_directories = project_directories.sort.uniq

        mappings = {}

        project_directories.each do |dir|
          if File.directory?("#{dir}/.git")
            mappings[dir] = 'Git'
          elsif File.directory?("#{dir}/.svn")
            mappings[dir] = 'svn'
          end
        end

        return nil if 0 == mappings.size

        create_component('VcsDirectoryMappings') do |xml|
          mappings.each_pair do |dir, vcs_type|
            resolved_dir = resolve_path(dir)
            mapped_dir = resolved_dir == '$PROJECT_DIR$/.' ? buildr_project.base_dir : resolved_dir
            xml.mapping :directory => mapped_dir, :vcs => vcs_type
          end
        end
      end

      def data_sources_component
        create_composite_component('DataSourceManagerImpl', {:format => 'xml', :hash => '3208837817'}, self.data_sources)
      end

      def artifacts_component
        create_composite_component('ArtifactManager', {}, self.artifacts)
      end

      def configurations_component
        create_composite_component('ProjectRunConfigurationManager', {}, self.configurations)
      end

      def resolve_path(path)
        resolve_path_from_base(path, '$PROJECT_DIR$')
      end

    private

      def to_artifact_name(project, options)
        options[:name] || project.iml.id
      end

      def build_on_make(options)
        options[:build_on_make].nil? ? false : options[:build_on_make]
      end

      def emit_jar_artifacts(xml, artifacts)
        artifacts.each do |p|
          xml.element :id => 'artifact', 'artifact-name' => "#{p}.jar"
        end
      end

      def emit_libraries(xml, libraries)
        libraries.each(&:invoke).map(&:to_s).each do |dependency_path|
          xml.element :id => 'file-copy', :path => resolve_path(dependency_path)
        end
      end

      def emit_module_output(xml, projects)
        projects.each do |p|
          xml.element :id => 'module-output', :name => p.iml.name
        end
      end

      def emit_output_path(xml, artifact_name, options)
        ## The content here can not be indented
        output_dir = options[:output_dir] || buildr_project._(:artifacts, artifact_name)
        xml.tag!('output-path', resolve_path(output_dir))
      end

      def emit_ejb_descriptors(xml, project, options)
        if options[:enable_ejb] || (options[:ejb_module_names] && options[:ejb_module_names].size > 0)
          module_names = options[:ejb_module_names] || [project.iml.name]
          module_names.each do |module_name|
            facet_name = options[:ejb_facet_name] || 'EJB'
            xml.element :id => 'javaee-facet-resources', :facet => "#{module_name}/ejb/#{facet_name}"
          end
        end
      end

      def emit_jpa_descriptors(xml, project, options)
        if options[:enable_jpa] || (options[:jpa_module_names] && options[:jpa_module_names].size > 0)
          module_names = options[:jpa_module_names] || [project.iml.name]
          module_names.each do |module_name|
            facet_name = options[:jpa_facet_name] || 'JPA'
            xml.element :id => 'jpa-descriptors', :facet => "#{module_name}/jpa/#{facet_name}"
          end
        end
      end

      def encrypt(password)
        password.bytes.inject('') { |x, y| x + (y ^ 0xdfaa).to_s(16) }
      end

      def partition_dependencies(dependencies)
        libraries = []
        projects = []

        dependencies.each do |dependency|
          artifacts = Buildr.artifacts(dependency)
          artifacts_as_strings = artifacts.map(&:to_s)
          all_projects = Buildr::Project.instance_variable_get('@projects').keys
          project = Buildr.projects(all_projects).detect do |project|
            [project.packages, project.compile.target, project.resources.target, project.test.compile.target, project.test.resources.target].flatten.
              detect { |component| artifacts_as_strings.include?(component.to_s) }
          end
          if project
            projects << project
          else
            libraries += artifacts
          end
        end
        return libraries.uniq, projects.uniq
      end
    end

    module ProjectExtension
      include Extension

      first_time do
        desc 'Generate Intellij IDEA artifacts for all projects'
        Project.local_task 'idea' => 'artifacts'

        desc 'Delete the generated Intellij IDEA artifacts'
        Project.local_task 'idea:clean'
      end

      before_define do |project|
        project.recursive_task('idea')
        project.recursive_task('idea:clean')
      end

      after_define do |project|
        idea = project.task('idea')

        files = [
          (project.iml if project.iml?),
          (project.ipr if project.ipr?)
        ].compact

        files.each do |ideafile|
          module_dir =  File.dirname(ideafile.filename)
          idea.enhance do |task|
            mkdir_p module_dir
            info "Writing #{ideafile.filename}"
            t = Tempfile.open('buildr-idea')
            temp_filename = t.path
            t.close!
            File.open(temp_filename, 'w') do |f|
              ideafile.write f
            end
            mv temp_filename, ideafile.filename
          end
          if project.ipr?
            filename = project._("#{project.ipr.name}.ids")
            rm_rf filename if File.exists?(filename)
          end
        end

        project.task('idea:clean') do
          files.each do |f|
            info "Removing #{f.filename}" if File.exist?(f.filename)
            rm_rf f.filename
          end
        end
      end

      def ipr
        if ipr?
          @ipr ||= IdeaProject.new(self)
        else
          raise 'Only the root project has an IPR'
        end
      end

      def ipr?
        !@no_ipr && self.parent.nil?
      end

      def iml
        if iml?
          unless @iml
            inheritable_iml_source = self.parent
            while inheritable_iml_source && !inheritable_iml_source.iml?
              inheritable_iml_source = inheritable_iml_source.parent;
            end
            @iml = inheritable_iml_source ? inheritable_iml_source.iml.clone : IdeaModule.new
            @iml.buildr_project = self
          end
          return @iml
        else
          raise "IML generation is disabled for #{self.name}"
        end
      end

      def no_ipr
        @no_ipr = true
      end

      def no_iml
        @has_iml = false
      end

      def iml?
        @has_iml = @has_iml.nil? ? true : @has_iml
      end
    end
  end
end

class Buildr::Project
  include Buildr::IntellijIdea::ProjectExtension
end
