// Copyright 2014 Unknwon
//
// Licensed 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.

package ini_test

import (
	"bytes"
	"io/ioutil"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
	"gopkg.in/ini.v1"
)

const (
	_CONF_DATA = `
	; Package name
	NAME        = ini
	; Package version
	VERSION     = v1
	; Package import path
	IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
	
	# Information about package author
	# Bio can be written in multiple lines.
	[author]
	NAME   = Unknwon  ; Succeeding comment
	E-MAIL = fake@localhost
	GITHUB = https://github.com/%(NAME)s
	BIO    = """Gopher.
	Coding addict.
	Good man.
	"""  # Succeeding comment`
	_MINIMAL_CONF   = "testdata/minimal.ini"
	_FULL_CONF      = "testdata/full.ini"
	_NOT_FOUND_CONF = "testdata/404.ini"
)

func TestLoad(t *testing.T) {
	Convey("Load from good data sources", t, func() {
		f, err := ini.Load([]byte(`
NAME = ini
VERSION = v1
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s`),
			"testdata/minimal.ini",
			ioutil.NopCloser(bytes.NewReader([]byte(`
[author]
NAME = Unknwon
`))),
		)
		So(err, ShouldBeNil)
		So(f, ShouldNotBeNil)

		// Vaildate values make sure all sources are loaded correctly
		sec := f.Section("")
		So(sec.Key("NAME").String(), ShouldEqual, "ini")
		So(sec.Key("VERSION").String(), ShouldEqual, "v1")
		So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")

		sec = f.Section("author")
		So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
		So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
	})

	Convey("Load from bad data sources", t, func() {
		Convey("Invalid input", func() {
			_, err := ini.Load(_NOT_FOUND_CONF)
			So(err, ShouldNotBeNil)
		})

		Convey("Unsupported type", func() {
			_, err := ini.Load(123)
			So(err, ShouldNotBeNil)
		})
	})
}

func TestLoadSources(t *testing.T) {
	Convey("Load from data sources with options", t, func() {
		Convey("Ignore nonexistent files", func() {
			f, err := ini.LooseLoad(_NOT_FOUND_CONF, _MINIMAL_CONF)
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			Convey("Inverse case", func() {
				_, err = ini.Load(_NOT_FOUND_CONF)
				So(err, ShouldNotBeNil)
			})
		})

		Convey("Insensitive to section and key names", func() {
			f, err := ini.InsensitiveLoad(_MINIMAL_CONF)
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")

			Convey("Write out", func() {
				var buf bytes.Buffer
				_, err := f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `[author]
e-mail = u@gogs.io

`)
			})

			Convey("Inverse case", func() {
				f, err := ini.Load(_MINIMAL_CONF)
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
			})
		})

		Convey("Ignore continuation lines", func() {
			f, err := ini.LoadSources(ini.LoadOptions{
				IgnoreContinuation: true,
			}, []byte(`
key1=a\b\
key2=c\d\
key3=value`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
			So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
			So(f.Section("").Key("key3").String(), ShouldEqual, "value")

			Convey("Inverse case", func() {
				f, err := ini.Load([]byte(`
key1=a\b\
key2=c\d\`))
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
			})
		})

		Convey("Ignore inline comments", func() {
			f, err := ini.LoadSources(ini.LoadOptions{
				IgnoreInlineComment: true,
			}, []byte(`
key1=value ;comment
key2=value2 #comment2`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
			So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)

			Convey("Inverse case", func() {
				f, err := ini.Load([]byte(`
key1=value ;comment
key2=value2 #comment2`))
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
				So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
				So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
				So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
			})
		})

		Convey("Allow boolean type keys", func() {
			f, err := ini.LoadSources(ini.LoadOptions{
				AllowBooleanKeys: true,
			}, []byte(`
key1=hello
#key2
key3`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
			So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)

			Convey("Write out", func() {
				var buf bytes.Buffer
				_, err := f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `key1 = hello
# key2
key3
`)
			})

			Convey("Inverse case", func() {
				_, err := ini.Load([]byte(`
key1=hello
#key2
key3`))
				So(err, ShouldNotBeNil)
			})
		})

		Convey("Allow shadow keys", func() {
			f, err := ini.ShadowLoad([]byte(`
[remote "origin"]
url = https://github.com/Antergone/test1.git
url = https://github.com/Antergone/test2.git
fetch = +refs/heads/*:refs/remotes/origin/*`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
			So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
				"https://github.com/Antergone/test1.git",
				"https://github.com/Antergone/test2.git",
			})
			So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")

			Convey("Write out", func() {
				var buf bytes.Buffer
				_, err := f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `[remote "origin"]
url   = https://github.com/Antergone/test1.git
url   = https://github.com/Antergone/test2.git
fetch = +refs/heads/*:refs/remotes/origin/*

`)
			})

			Convey("Inverse case", func() {
				f, err := ini.Load([]byte(`
[remote "origin"]
url = https://github.com/Antergone/test1.git
url = https://github.com/Antergone/test2.git`))
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
			})
		})

		Convey("Unescape double quotes inside value", func() {
			f, err := ini.LoadSources(ini.LoadOptions{
				UnescapeValueDoubleQuotes: true,
			}, []byte(`
create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)

			Convey("Inverse case", func() {
				f, err := ini.Load([]byte(`
create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
			})
		})

		Convey("Unescape comment symbols inside value", func() {
			f, err := ini.LoadSources(ini.LoadOptions{
				IgnoreInlineComment:         true,
				UnescapeValueCommentSymbols: true,
			}, []byte(`
key = test value <span style="color: %s\; background: %s">more text</span>
`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
		})

		Convey("Allow unparseable sections", func() {
			f, err := ini.LoadSources(ini.LoadOptions{
				Insensitive:         true,
				UnparseableSections: []string{"core_lesson", "comments"},
			}, []byte(`
Lesson_Location = 87
Lesson_Status = C
Score = 3
Time = 00:02:30

[CORE_LESSON]
my lesson state data – 1111111111111111111000000000000000001110000
111111111111111111100000000000111000000000 – end my lesson state data

[COMMENTS]
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("").Key("score").String(), ShouldEqual, "3")
			So(f.Section("").Body(), ShouldBeEmpty)
			So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
111111111111111111100000000000111000000000 – end my lesson state data`)
			So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)

			Convey("Write out", func() {
				var buf bytes.Buffer
				_, err := f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `lesson_location = 87
lesson_status   = C
score           = 3
time            = 00:02:30

[core_lesson]
my lesson state data – 1111111111111111111000000000000000001110000
111111111111111111100000000000111000000000 – end my lesson state data

[comments]
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
`)
			})

			Convey("Inverse case", func() {
				_, err := ini.Load([]byte(`
[CORE_LESSON]
my lesson state data – 1111111111111111111000000000000000001110000
111111111111111111100000000000111000000000 – end my lesson state data`))
				So(err, ShouldNotBeNil)
			})
		})
	})
}