package revision

import (
	"bytes"
	"regexp"
	"time"

	. "gopkg.in/check.v1"
)

type ParserSuite struct{}

var _ = Suite(&ParserSuite{})

func (s *ParserSuite) TestErrInvalidRevision(c *C) {
	e := ErrInvalidRevision{"test"}

	c.Assert(e.Error(), Equals, "Revision invalid : test")
}

func (s *ParserSuite) TestNewParserFromString(c *C) {
	p := NewParserFromString("test")

	c.Assert(p, FitsTypeOf, &Parser{})
}

func (s *ParserSuite) TestScan(c *C) {
	parser := NewParser(bytes.NewBufferString("Hello world !"))

	expected := []struct {
		t token
		s string
	}{
		{
			word,
			"Hello",
		},
		{
			space,
			" ",
		},
		{
			word,
			"world",
		},
		{
			space,
			" ",
		},
		{
			emark,
			"!",
		},
	}

	for i := 0; ; {
		tok, str, err := parser.scan()

		if tok == eof {
			return
		}

		c.Assert(err, Equals, nil)
		c.Assert(str, Equals, expected[i].s)
		c.Assert(tok, Equals, expected[i].t)

		i++
	}
}

func (s *ParserSuite) TestUnscan(c *C) {
	parser := NewParser(bytes.NewBufferString("Hello world !"))

	tok, str, err := parser.scan()

	c.Assert(err, Equals, nil)
	c.Assert(str, Equals, "Hello")
	c.Assert(tok, Equals, word)

	parser.unscan()

	tok, str, err = parser.scan()

	c.Assert(err, Equals, nil)
	c.Assert(str, Equals, "Hello")
	c.Assert(tok, Equals, word)
}

func (s *ParserSuite) TestParseWithValidExpression(c *C) {
	tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z")

	datas := map[string]Revisioner{
		"@": []Revisioner{Ref("HEAD")},
		"@~3": []Revisioner{
			Ref("HEAD"),
			TildePath{3},
		},
		"@{2016-12-16T21:42:47Z}": []Revisioner{AtDate{tim}},
		"@{1}":                    []Revisioner{AtReflog{1}},
		"@{-1}":                   []Revisioner{AtCheckout{1}},
		"master@{upstream}": []Revisioner{
			Ref("master"),
			AtUpstream{},
		},
		"@{upstream}": []Revisioner{
			AtUpstream{},
		},
		"@{u}": []Revisioner{
			AtUpstream{},
		},
		"master@{push}": []Revisioner{
			Ref("master"),
			AtPush{},
		},
		"master@{2016-12-16T21:42:47Z}": []Revisioner{
			Ref("master"),
			AtDate{tim},
		},
		"HEAD^": []Revisioner{
			Ref("HEAD"),
			CaretPath{1},
		},
		"master~3": []Revisioner{
			Ref("master"),
			TildePath{3},
		},
		"v0.99.8^{commit}": []Revisioner{
			Ref("v0.99.8"),
			CaretType{"commit"},
		},
		"v0.99.8^{}": []Revisioner{
			Ref("v0.99.8"),
			CaretType{"tag"},
		},
		"HEAD^{/fix nasty bug}": []Revisioner{
			Ref("HEAD"),
			CaretReg{regexp.MustCompile("fix nasty bug"), false},
		},
		":/fix nasty bug": []Revisioner{
			ColonReg{regexp.MustCompile("fix nasty bug"), false},
		},
		"HEAD:README": []Revisioner{
			Ref("HEAD"),
			ColonPath{"README"},
		},
		":README": []Revisioner{
			ColonPath{"README"},
		},
		"master:./README": []Revisioner{
			Ref("master"),
			ColonPath{"./README"},
		},
		"master^1~:./README": []Revisioner{
			Ref("master"),
			CaretPath{1},
			TildePath{1},
			ColonPath{"./README"},
		},
		":0:README": []Revisioner{
			ColonStagePath{"README", 0},
		},
		":3:README": []Revisioner{
			ColonStagePath{"README", 3},
		},
		"master~1^{/update}~5~^^1": []Revisioner{
			Ref("master"),
			TildePath{1},
			CaretReg{regexp.MustCompile("update"), false},
			TildePath{5},
			TildePath{1},
			CaretPath{1},
			CaretPath{1},
		},
	}

	for d, expected := range datas {
		parser := NewParser(bytes.NewBufferString(d))

		result, err := parser.Parse()

		c.Assert(err, Equals, nil)
		c.Assert(result, DeepEquals, expected)
	}
}

func (s *ParserSuite) TestParseWithUnValidExpression(c *C) {
	datas := map[string]error{
		"..":                              &ErrInvalidRevision{`must not start with "."`},
		"master^1master":                  &ErrInvalidRevision{`reference must be defined once at the beginning`},
		"master^1@{2016-12-16T21:42:47Z}": &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{<ISO-8601 date>}, @{<ISO-8601 date>}`},
		"master^1@{1}":                    &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{<n>}, @{<n>}`},
		"master@{-1}":                     &ErrInvalidRevision{`"@" statement is not valid, could be : @{-<n>}`},
		"master^1@{upstream}":             &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{upstream}, @{upstream}, <refname>@{u}, @{u}`},
		"master^1@{u}":                    &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{upstream}, @{upstream}, <refname>@{u}, @{u}`},
		"master^1@{push}":                 &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{push}, @{push}`},
		"^1":                              &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`},
		"^{/test}":                        &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`},
		"~1":                              &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`},
		"master:/test":                    &ErrInvalidRevision{`":" statement is not valid, could be : :/<regexp>`},
		"master:0:README":                 &ErrInvalidRevision{`":" statement is not valid, could be : :<n>:<path>`},
	}

	for s, e := range datas {
		parser := NewParser(bytes.NewBufferString(s))
		_, err := parser.Parse()
		c.Assert(err, DeepEquals, e)
	}
}

func (s *ParserSuite) TestParseAtWithValidExpression(c *C) {
	tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z")

	datas := map[string]Revisioner{
		"":                       Ref("HEAD"),
		"{1}":                    AtReflog{1},
		"{-1}":                   AtCheckout{1},
		"{push}":                 AtPush{},
		"{upstream}":             AtUpstream{},
		"{u}":                    AtUpstream{},
		"{2016-12-16T21:42:47Z}": AtDate{tim},
	}

	for d, expected := range datas {
		parser := NewParser(bytes.NewBufferString(d))

		result, err := parser.parseAt()

		c.Assert(err, Equals, nil)
		c.Assert(result, DeepEquals, expected)
	}
}

func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) {
	datas := map[string]error{
		"{test}": &ErrInvalidRevision{`wrong date "test" must fit ISO-8601 format : 2006-01-02T15:04:05Z`},
		"{-1":    &ErrInvalidRevision{`missing "}" in @{-n} structure`},
	}

	for s, e := range datas {
		parser := NewParser(bytes.NewBufferString(s))

		_, err := parser.parseAt()

		c.Assert(err, DeepEquals, e)
	}
}

func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) {
	datas := map[string]Revisioner{
		"":                    CaretPath{1},
		"2":                   CaretPath{2},
		"{}":                  CaretType{"tag"},
		"{commit}":            CaretType{"commit"},
		"{tree}":              CaretType{"tree"},
		"{blob}":              CaretType{"blob"},
		"{tag}":               CaretType{"tag"},
		"{object}":            CaretType{"object"},
		"{/hello world !}":    CaretReg{regexp.MustCompile("hello world !"), false},
		"{/!-hello world !}":  CaretReg{regexp.MustCompile("hello world !"), true},
		"{/!! hello world !}": CaretReg{regexp.MustCompile("! hello world !"), false},
	}

	for d, expected := range datas {
		parser := NewParser(bytes.NewBufferString(d))

		result, err := parser.parseCaret()

		c.Assert(err, Equals, nil)
		c.Assert(result, DeepEquals, expected)
	}
}

func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) {
	datas := map[string]error{
		"3":         &ErrInvalidRevision{`"3" found must be 0, 1 or 2 after "^"`},
		"{test}":    &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`},
		"{/!test}":  &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`},
		"{/test**}": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: invalid nested repetition operator: `**`"},
	}

	for s, e := range datas {
		parser := NewParser(bytes.NewBufferString(s))

		_, err := parser.parseCaret()

		c.Assert(err, DeepEquals, e)
	}
}

func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) {
	datas := map[string]Revisioner{
		"3": TildePath{3},
		"1": TildePath{1},
		"":  TildePath{1},
	}

	for d, expected := range datas {
		parser := NewParser(bytes.NewBufferString(d))

		result, err := parser.parseTilde()

		c.Assert(err, Equals, nil)
		c.Assert(result, DeepEquals, expected)
	}
}

func (s *ParserSuite) TestParseColonWithValidExpression(c *C) {
	datas := map[string]Revisioner{
		"/hello world !":    ColonReg{regexp.MustCompile("hello world !"), false},
		"/!-hello world !":  ColonReg{regexp.MustCompile("hello world !"), true},
		"/!! hello world !": ColonReg{regexp.MustCompile("! hello world !"), false},
		"../parser.go":      ColonPath{"../parser.go"},
		"./parser.go":       ColonPath{"./parser.go"},
		"parser.go":         ColonPath{"parser.go"},
		"0:parser.go":       ColonStagePath{"parser.go", 0},
		"1:parser.go":       ColonStagePath{"parser.go", 1},
		"2:parser.go":       ColonStagePath{"parser.go", 2},
		"3:parser.go":       ColonStagePath{"parser.go", 3},
	}

	for d, expected := range datas {
		parser := NewParser(bytes.NewBufferString(d))

		result, err := parser.parseColon()

		c.Assert(err, Equals, nil)
		c.Assert(result, DeepEquals, expected)
	}
}

func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) {
	datas := map[string]error{
		"/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`},
		"/*":     &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: missing argument to repetition operator: `*`"},
	}

	for s, e := range datas {
		parser := NewParser(bytes.NewBufferString(s))

		_, err := parser.parseColon()

		c.Assert(err, DeepEquals, e)
	}
}

func (s *ParserSuite) TestParseRefWithValidName(c *C) {
	datas := []string{
		"lock",
		"master",
		"v1.0.0",
		"refs/stash",
		"refs/tags/v1.0.0",
		"refs/heads/master",
		"refs/remotes/test",
		"refs/remotes/origin/HEAD",
		"refs/remotes/origin/master",
		"0123abcd", // short hash
	}

	for _, d := range datas {
		parser := NewParser(bytes.NewBufferString(d))

		result, err := parser.parseRef()

		c.Assert(err, Equals, nil)
		c.Assert(result, Equals, Ref(d))
	}
}

func (s *ParserSuite) TestParseRefWithInvalidName(c *C) {
	datas := map[string]error{
		".master":                     &ErrInvalidRevision{`must not start with "."`},
		"/master":                     &ErrInvalidRevision{`must not start with "/"`},
		"master/":                     &ErrInvalidRevision{`must not end with "/"`},
		"master.":                     &ErrInvalidRevision{`must not end with "."`},
		"refs/remotes/.origin/HEAD":   &ErrInvalidRevision{`must not contains "/."`},
		"test..test":                  &ErrInvalidRevision{`must not contains ".."`},
		"test..":                      &ErrInvalidRevision{`must not contains ".."`},
		"test test":                   &ErrInvalidRevision{`must not contains " "`},
		"test*test":                   &ErrInvalidRevision{`must not contains "*"`},
		"test?test":                   &ErrInvalidRevision{`must not contains "?"`},
		"test\\test":                  &ErrInvalidRevision{`must not contains "\"`},
		"test[test":                   &ErrInvalidRevision{`must not contains "["`},
		"te//st":                      &ErrInvalidRevision{`must not contains consecutively "/"`},
		"refs/remotes/test.lock/HEAD": &ErrInvalidRevision{`cannot end with .lock`},
		"test.lock":                   &ErrInvalidRevision{`cannot end with .lock`},
	}

	for s, e := range datas {
		parser := NewParser(bytes.NewBufferString(s))

		_, err := parser.parseRef()

		c.Assert(err, DeepEquals, e)
	}
}
