require File.expand_path('../spec_helper', __FILE__)

describe Xcodeproj::Config do
  describe 'In general' do
    extend SpecHelper::TemporaryDirectory

    before do
      @hash = { 'OTHER_LDFLAGS' => '-framework "Foundation"' }
      @config = Xcodeproj::Config.new(@hash)
      @config_fixture = fixture_path('oneline-key-value.xcconfig')
      @config_with_include_fixture = fixture_path('config-with-include.xcconfig')
    end

    it 'can be created with hash' do
      config = Xcodeproj::Config.new(@hash)
      config.to_hash.should.be.equal @hash
    end

    it 'can be created with file path' do
      config = Xcodeproj::Config.new(@config_fixture)
      config.to_hash.should.be.equal @hash
    end

    it 'can be created with File instance' do
      xcconfig_file = File.new(@config_fixture)
      xcconfig = Xcodeproj::Config.new(xcconfig_file)
      xcconfig.to_hash.should.be.equal @hash
    end

    it 'does not equal if attributes change' do
      other_config = @config.dup
      other_config.should.be.equal @config
      other_config.attributes['SOME_KEY'] = 'SOME_VALUE'
      other_config.should.not == @config
    end

    it 'does not equal if other linker flags change change' do
      other_config = @config.dup
      other_config.should.be.equal @config
      other_config.other_linker_flags[:simple].add '--some-option'
      other_config.should.not == @config
    end

    it 'does not equal if includes change' do
      other_config = @config.dup
      other_config.should.be.equal @config
      other_config.includes.push 'Somefile'
      other_config.should.not == @config
    end

    it 'does not modifies the hash used for initialization' do
      original = @hash.dup
      Xcodeproj::Config.new(@hash)
      @hash.should.be.equal original
    end

    it 'parses the frameworks and the libraries' do
      hash = { 'OTHER_LDFLAGS' => '-framework Foundation -weak_framework Twitter -lxml2.2.7.3' }
      config = Xcodeproj::Config.new(hash)
      config.frameworks.to_a.should.be.equal %w(Foundation)
      config.weak_frameworks.to_a.should.be.equal %w(Twitter)
      config.libraries.to_a.should.be.equal %w(xml2.2.7.3)
    end

    it 'parses arg files with double quotes' do
      hash = { 'OTHER_LDFLAGS' => '@"${PODS_ROOT}/Target Support Files/path/to/other_ldflags"' }
      config = Xcodeproj::Config.new(hash)
      config.arg_files.to_a.should.be.equal ['${PODS_ROOT}/Target Support Files/path/to/other_ldflags']
    end

    it 'parses arg files with shell escaping' do
      hash = { 'OTHER_LDFLAGS' => '@${PODS_ROOT}/Target\ Support\ Files/path/to/other_ldflags' }
      config = Xcodeproj::Config.new(hash)
      config.arg_files.to_a.should.be.equal ['${PODS_ROOT}/Target Support Files/path/to/other_ldflags']
    end

    it 'can be serialized with #to_s' do
      @config.to_s.should.be.equal "OTHER_LDFLAGS = -framework \"Foundation\"\n"
    end

    it 'strips trailing whitespace when serializing with #to_s' do
      @config.attributes['FOO'] = 'BAR   '
      @config.to_s.should == "FOO = BAR\nOTHER_LDFLAGS = -framework \"Foundation\"\n"
    end

    it 'sorts the internal data by setting name when serializing with #to_s' do
      config = Xcodeproj::Config.new('Y' => '2', 'Z' => '3', 'X' => '1')
      config.to_s.should == "X = 1\nY = 2\nZ = 3\n"
    end

    it 'can serialize an empty config' do
      config = Xcodeproj::Config.new
      config.to_s.should == "\n"
    end

    it 'can be serialized with #to_hash' do
      @config.to_hash.should.be.equal @hash
    end

    it 'ignores settings that are only inherited in #to_hash' do
      config = Xcodeproj::Config.new('Y' => '$(inherited)', 'Z' => '${inherited}', 'X' => '$(inherited) 1')
      config.to_hash.should == { 'X' => '$(inherited) 1' }
    end

    it 'handles non-string settings in #to_s' do
      config = Xcodeproj::Config.new('X' => true, 'Y' => 123)
      config.to_s.should == <<-EOS
X = true
Y = 123
      EOS
    end

    it 'can prefix values during serialization' do
      @prefix_hash = { 'PODS_PREFIX_OTHER_LDFLAGS' => @hash['OTHER_LDFLAGS'] }
      @config.to_hash('PODS_PREFIX_').should.be.equal @prefix_hash
    end

    it 'does not serialize with #to_s when inspecting the object' do
      @config.inspect.should == @config.to_hash.inspect
    end

    it 'can be compared with other instances' do
      config_dupe = Xcodeproj::Config.new(@hash)
      config_dupe.should.be.equal @config
    end

    it 'merges another config hash in place' do
      @config.merge!('HEADER_SEARCH_PATHS' => '/some/path')
      @config.to_hash.should == {
        'OTHER_LDFLAGS' => '-framework "Foundation"',
        'HEADER_SEARCH_PATHS' => '/some/path',
      }
    end

    it 'merges another config hash in place with the `<<` shortcut' do
      @config << { 'HEADER_SEARCH_PATHS' => '/some/path' }
      @config.to_hash.should == {
        'OTHER_LDFLAGS' => '-framework "Foundation"',
        'HEADER_SEARCH_PATHS' => '/some/path',
      }

      @config << { 'HEADER_SEARCH_PATHS' => '/some/path/subdir', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) ' }
      @config.to_hash['HEADER_SEARCH_PATHS'].should == '/some/path /some/path/subdir'

      @config << { 'GCC_PREPROCESSOR_DEFINITIONS' => 'COCOAPODS=1' }
      @config.to_hash['GCC_PREPROCESSOR_DEFINITIONS'].should == '$(inherited) COCOAPODS=1'
    end

    it 'merges another hash in a new one' do
      new = @config.merge('HEADER_SEARCH_PATHS' => '/some/path')
      new.object_id.should.not == @config.object_id
      new.to_hash.should == {
        'OTHER_LDFLAGS' => '-framework "Foundation"',
        'HEADER_SEARCH_PATHS' => '/some/path',
      }
      @config.to_hash.should == { 'OTHER_LDFLAGS' => '-framework "Foundation"' }
    end

    it 'appends a value for the same key when merging' do
      @config.merge!('OTHER_LDFLAGS' => '-l xml2.2.7.3')
      @config.to_hash.should == {
        'OTHER_LDFLAGS' => '-l"xml2.2.7.3" -framework "Foundation"',
      }
    end

    it 'appends a value for the same key when merging' do
      @config.merge!('OTHER_LDFLAGS' => '-l xml2.2.7.3 @"${PODS_ROOT}/Target Support Files/other_ldflags"')
      @config.to_hash.should == {
        'OTHER_LDFLAGS' => '-l"xml2.2.7.3" -framework "Foundation" @"${PODS_ROOT}/Target Support Files/other_ldflags"',
      }
    end

    it 'de-duplicates values when merging' do
      @config << { 'FOO' => 'bar $(baz)' }
      @config.merge!('FOO' => 'bar $(baz)')
      @config.to_hash.should == {
        'FOO' => 'bar $(baz)',
        'OTHER_LDFLAGS' => '-framework "Foundation"',
      }
    end

    it 'de-duplicates a prefix when merging' do
      @config << { 'FOO' => 'bar' }
      @config.merge!('FOO' => 'bar $(baz)')
      @config.to_hash.should == {
        'FOO' => 'bar $(baz)',
        'OTHER_LDFLAGS' => '-framework "Foundation"',
      }
    end

    it 'generates the config file with refs to all included xcconfigs' do
      @config.includes = ['Somefile.xcconfig']
      @config.to_s.split("\n").first.should == '#include "Somefile.xcconfig"'
    end

    it 'appends the extension to the included files if needed' do
      @config.includes = ['Somefile']
      @config.to_s.split("\n").first.should == '#include "Somefile.xcconfig"'
    end

    it 'creates the config file' do
      @config.merge!('HEADER_SEARCH_PATHS' => '/some/path')
      @config.merge!('OTHER_LDFLAGS' => '-l xml2.2.7.3')
      @config.save_as(temporary_directory + 'Pods.xcconfig')
      (temporary_directory + 'Pods.xcconfig').read.split("\n").sort.should == [
        'OTHER_LDFLAGS = -l"xml2.2.7.3" -framework "Foundation"',
        'HEADER_SEARCH_PATHS = /some/path',
      ].sort
    end

    it 'does not rewrite the config file if contents have not changed' do
      filename = temporary_directory + 'Pods.xcconfig'

      def filename.open(mode)
        @writing_count ||= 0
        @writing_count += 1

        super
      end

      @config.save_as(filename)
      @config.save_as(filename)
      filename.instance_variable_get(:@writing_count).should == 1
    end

    it 'contains file path refs to all included xcconfigs' do
      config = Xcodeproj::Config.new(fixture_path('include.xcconfig'))
      config.includes.size.should.be.equal 1
      config.includes.first.should.be.equal 'Somefile.xcconfig'
    end

    it 'contains file path refs to optional included xcconfigs' do
      config = Xcodeproj::Config.new(fixture_path('optional-include.xcconfig'))
      config.includes.size.should.be.equal 1
      config.includes.first.should.be.equal 'Optional.xcconfig'
    end

    it 'contains file path refs to all included xcconfigs, even without the extension added' do
      config = Xcodeproj::Config.new(fixture_path('config-with-include-no-extension.xcconfig'))
      config.includes.size.should.be.equal 1
      config.includes.first.should.be.equal 'another-config-with-include.xcconfig'
    end

    it 'can be created from multiline file' do
      config = Xcodeproj::Config.new(fixture_path('sample.xcconfig'))
      config.to_hash.should == {
        'Key1' => 'Value1 Value2',
        'Key2' => 'Value3 Value4 Value5',
        'Key3' => 'Value6',
        'Key4' => '',
      }
    end

    it 'can be created from file with comments inside' do
      config = Xcodeproj::Config.new(fixture_path('with-comments.xcconfig'))
      config.to_hash.should == { 'Key' => 'Value' }
    end

    it 'can be created from file with subscripts' do
      config = Xcodeproj::Config.new(fixture_path('subscript.xcconfig'))
      config.to_hash.should == {
        'CODE_SIGN_IDENTITY[sdk=iphoneos*]' => 'iPhone Developer',
      }
    end

    it 'can be created from file with differing whitespace' do
      config = Xcodeproj::Config.new(fixture_path('differing-whitespace.xcconfig'))
      config.to_hash.should == {
        'Key1' => 'Value1',
        'Key2' => 'Value2',
        'Key3' => 'Value3',
        'Key4[sub1=sub2]' => 'Value4',
        'Key5[sub3=sub4]' => 'Value5',
      }
    end

    it "doesn't duplicate libraries and frameworks" do
      hash = { 'OTHER_LDFLAGS' => '-framework Foundation -weak_framework Twitter -lxml2.2.7.3' }
      config = Xcodeproj::Config.new(hash)
      # merge the original hash again
      config.merge!(hash)
      config.frameworks.add 'Foundation'
      config.weak_frameworks.add 'Twitter'
      config.libraries.add 'xml2.2.7.3'
      config.frameworks.to_a.should.be.equal %w(Foundation)
      config.weak_frameworks.to_a.should.be.equal %w(Twitter)
      config.libraries.to_a.should.be.equal %w(xml2.2.7.3)
    end

    it "doesn't duplicate other attribute values" do
      hash = { 'CLANG_CXX_LIBRARY' => 'libc++' }
      config = Xcodeproj::Config.new(hash)
      config.merge!(hash)
      config.to_hash.should == hash
      config.merge!(config)
      config.to_hash.should == hash
    end

    it 'it preserves OTHER_LDFLAGS not related to libraries and frameworks' do
      config1 = Xcodeproj::Config.new('OTHER_LDFLAGS' => %w(-ObjC -fobjc-arc).join(' '))
      config2 = Xcodeproj::Config.new('OTHER_LDFLAGS' => '-framework SystemConfiguration')
      config1.merge! config2
      config1.to_hash['OTHER_LDFLAGS'].should == '-ObjC -fobjc-arc -framework "SystemConfiguration"'
    end

    it 'merges frameworks and libraries from another Config instance' do
      hash = { 'OTHER_LDFLAGS' => '-framework Foundation -weak_framework Twitter -lxml2.2.7.3' }
      config = Xcodeproj::Config.new
      config.merge!(Xcodeproj::Config.new(hash))
      config.frameworks.to_a.should.be.equal %w(Foundation)
      config.weak_frameworks.to_a.should.be.equal %w(Twitter)
      config.libraries.to_a.should.be.equal %w(xml2.2.7.3)
    end

    it 'specifies libraries in the OTHER_LDFLAGS in a format compatible with libtool' do
      hash = { 'OTHER_LDFLAGS' => '-lxml2.2.7.3' }
      config = Xcodeproj::Config.new(hash)
      config.to_hash['OTHER_LDFLAGS'].should.not == '-l "xml2.2.7.3"'
      config.to_hash['OTHER_LDFLAGS'].should == '-l"xml2.2.7.3"'
    end

    it 'does not drop the -force_load argument in case of repeated use' do
      config = Xcodeproj::Config.new
      config.merge!('OTHER_LDFLAGS' => '-force_load a')
      config.merge!('OTHER_LDFLAGS' => '-l"b"')
      config.merge!('OTHER_LDFLAGS' => '-force_load c')
      config.to_hash['OTHER_LDFLAGS'].should == '-l"b" -force_load a -force_load c'
    end

    it 'does not mix up the location of the -force_load argument' do
      config = Xcodeproj::Config.new
      config.merge!('OTHER_LDFLAGS' => '-ObjC')
      config.merge!('OTHER_LDFLAGS' => '-force_load $(PODS_ROOT)/Intercom/Intercom/libIntercom.a')
      config.merge!('OTHER_LDFLAGS' => '-l"Pods-GoogleAnalytics-iOS-SDK"')
      config.merge!('OTHER_LDFLAGS' => '-l"Pods-Intercom"')
      config.to_hash['OTHER_LDFLAGS'].should == '-ObjC -l"Pods-GoogleAnalytics-iOS-SDK" -l"Pods-Intercom" -force_load $(PODS_ROOT)/Intercom/Intercom/libIntercom.a'
    end

    it 'processes include statements if initialized with filepath' do
      xcconfig_file = File.new(@config_with_include_fixture)
      xcconfig = Xcodeproj::Config.new(xcconfig_file)
      xcconfig.to_hash.should == {
        'FIRST_BUILD_SETTING' => 'YES',
        'SECOND_BUILD_SETTING' => 'YES',
        'THIRD_BUILD_SETTING' => 'YES',
        'A_BUILD_SETTING' => 'OVERRIDE',
        'ANOTHER_BUILD_SETTING' => 'SHOULD_PRECEDE',
      }
    end

    it 'allows using ${inherited} when there are multiple instances of a key in the file' do
      file = stub(:read => "A = 1\nA=2 ${inherited} ${inherited} 2\n", :to_path => 'A.xcconfig')
      xcconfig = Xcodeproj::Config.new(file)
      xcconfig.to_hash.should == { 'A' => '2 1 1 2' }

      file = fixture_path('config-with-inherited-from-self.xcconfig')
      xcconfig = Xcodeproj::Config.new(file)
      xcconfig.to_hash.should == { 'HEADER_SEARCH_PATHS' => '${inherited} a b c d' }
    end
  end

  #---------------------------------------------------------------------------#

  describe 'Private helpers' do
    before do
      @config = Xcodeproj::Config.new
    end

    describe '#normalized_xcconfig_path' do
      it 'appends the extension if needed' do
        normalized_path = @config.send(:normalized_xcconfig_path, 'path/file')
        normalized_path.should == 'path/file.xcconfig'
      end

      it "doesn't append the extension if not needed" do
        normalized_path = @config.send(:normalized_xcconfig_path, 'path/file')
        normalized_path.should == 'path/file.xcconfig'
      end
    end
  end

  #---------------------------------------------------------------------------#
end
