// Copyright © 2016 Abcum Ltd
//
// 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 sql

func (p *parser) parseSelectStatement() (stmt *SelectStatement, err error) {

	stmt = &SelectStatement{}

	if stmt.Expr, err = p.parseFields(); err != nil {
		return nil, err
	}

	_, _, err = p.shouldBe(FROM)
	if err != nil {
		return nil, err
	}

	if stmt.What, err = p.parseWhat(); err != nil {
		return nil, err
	}

	if stmt.Cond, err = p.parseCond(); err != nil {
		return nil, err
	}

	if stmt.Split, err = p.parseSplit(); err != nil {
		return nil, err
	}

	if stmt.Group, err = p.parseGroup(); err != nil {
		return nil, err
	}

	if stmt.Order, err = p.parseOrder(); err != nil {
		return nil, err
	}

	if stmt.Limit, err = p.parseLimit(); err != nil {
		return nil, err
	}

	if stmt.Start, err = p.parseStart(); err != nil {
		return nil, err
	}

	if stmt.Fetch, err = p.parseFetch(); err != nil {
		return nil, err
	}

	if stmt.Version, err = p.parseVersion(); err != nil {
		return nil, err
	}

	if stmt.Timeout, err = p.parseTimeout(); err != nil {
		return nil, err
	}

	if err = checkExpression(aggrs, stmt.Expr, stmt.Group); err != nil {
		return nil, err
	}

	// If this query has any subqueries which
	// need to alter the database then mark
	// this query as a writeable statement.

	stmt.RW = p.buf.rw

	return

}

func (p *parser) parseFields() (mul Fields, err error) {

	for {

		one := &Field{}

		one.Expr, err = p.parseExpr()
		if err != nil {
			return
		}

		// Chec to see if the next token is an AS
		// clause, and if it is read the defined
		// field alias name from the scanner.

		if _, _, exi := p.mightBe(AS); exi {

			if _, one.Alias, err = p.shouldBe(IDENT, EXPR); err != nil {
				return nil, &ParseError{Found: one.Alias, Expected: []string{"alias name"}}
			}

			one.Field = one.Alias

		} else {

			switch v := one.Expr.(type) {
			case *Param:
				one.Field = v.VA
			case *Ident:
				one.Field = v.VA
			case *Value:
				one.Field = v.VA
			default:
				one.Field = one.String()
			}

		}

		// Append the single expression to the array
		// of return statement expressions.

		mul = append(mul, one)

		// Check to see if the next token is a comma
		// and if not, then break out of the loop,
		// otherwise repeat until we find no comma.

		if _, _, exi := p.mightBe(COMMA); !exi {
			break
		}

	}

	return

}

func (p *parser) parseSplit() (Idents, error) {

	// The next token that we expect to see is a
	// SPLIT token, and if we don't find one then
	// return nil, with no error.

	if _, _, exi := p.mightBe(SPLIT); !exi {
		return nil, nil
	}

	// We don't need to have a ON token, but we
	// allow it so that the SQL query would read
	// better when compared to english.

	_, _, _ = p.mightBe(ON)

	return p.parseIdioms()

}

func (p *parser) parseGroup() (mul Groups, err error) {

	// The next token that we expect to see is a
	// GROUP token, and if we don't find one then
	// return nil, with no error.

	if _, _, exi := p.mightBe(GROUP); !exi {
		return nil, nil
	}

	// We don't need to have a BY token, but we
	// allow it so that the SQL query would read
	// better when compared to english.

	_, _, _ = p.mightBe(BY)

	// If the next token is the ALL keyword then
	// we will group all records together, which
	// will prevent grouping by other fields.

	if _, _, exi := p.mightBe(ALL); exi {

		mul = append(mul, &Group{
			Expr: new(All),
		})

		return

	}

	// Otherwise let's parse the fields with which
	// we will group the selected data, with
	// multiple fields grouped by commas.

	for {

		var tok Token
		var lit string

		one := &Group{}

		tok, lit, err = p.shouldBe(IDENT, EXPR)
		if err != nil {
			return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
		}

		one.Expr, err = p.declare(tok, lit)
		if err != nil {
			return nil, err
		}

		// Append the single expression to the array
		// of return statement expressions.

		mul = append(mul, one)

		// Check to see if the next token is a comma
		// and if not, then break out of the loop,
		// otherwise repeat until we find no comma.

		if _, _, exi := p.mightBe(COMMA); !exi {
			break
		}

	}

	return

}

func (p *parser) parseOrder() (mul Orders, err error) {

	// The next token that we expect to see is a
	// ORDER token, and if we don't find one then
	// return nil, with no error.

	if _, _, exi := p.mightBe(ORDER); !exi {
		return nil, nil
	}

	// We don't need to have a BY token, but we
	// allow it so that the SQL query would read
	// better when compared to english.

	_, _, _ = p.mightBe(BY)

	for {

		var exi bool
		var tok Token
		var lit string

		one := &Order{}

		tok, lit, err = p.shouldBe(IDENT, EXPR, RAND)
		if err != nil {
			return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
		}

		switch tok {
		default:
			one.Expr, err = p.declare(tok, lit)
			if err != nil {
				return nil, err
			}
		case RAND:
			one.Expr = &FuncExpression{Name: "rand"}
			if _, _, exi = p.mightBe(LPAREN); exi {
				_, _, err = p.shouldBe(RPAREN)
				if err != nil {
					return nil, err
				}
			}
		}

		if _, _, exi = p.mightBe(COLLATE); exi {
			one.Tag, err = p.parseLanguage()
			if err != nil {
				return nil, err
			}
		}

		if tok, _, exi = p.mightBe(ASC, DESC); exi {
			one.Dir = (tok == ASC)
		} else {
			one.Dir = true
		}

		// Append the single expression to the array
		// of return statement expressions.

		mul = append(mul, one)

		// Check to see if the next token is a comma
		// and if not, then break out of the loop,
		// otherwise repeat until we find no comma.

		if _, _, exi := p.mightBe(COMMA); !exi {
			break
		}

	}

	return

}

func (p *parser) parseLimit() (Expr, error) {

	// The next token that we expect to see is a
	// LIMIT token, and if we don't find one then
	// return nil, with no error.

	if _, _, exi := p.mightBe(LIMIT); !exi {
		return nil, nil
	}

	// We don't need to have a BY token, but we
	// allow it so that the SQL query would read
	// better when compared to english.

	_, _, _ = p.mightBe(BY)

	tok, lit, err := p.shouldBe(NUMBER, PARAM)
	if err != nil {
		return nil, &ParseError{Found: lit, Expected: []string{"limit number"}}
	}

	return p.declare(tok, lit)

}

func (p *parser) parseStart() (Expr, error) {

	// The next token that we expect to see is a
	// START token, and if we don't find one then
	// return nil, with no error.

	if _, _, exi := p.mightBe(START); !exi {
		return nil, nil
	}

	// We don't need to have a AT token, but we
	// allow it so that the SQL query would read
	// better when compared to english.

	_, _, _ = p.mightBe(AT)

	tok, lit, err := p.shouldBe(NUMBER, PARAM)
	if err != nil {
		return nil, &ParseError{Found: lit, Expected: []string{"start number"}}
	}

	return p.declare(tok, lit)

}

func (p *parser) parseFetch() (mul Fetchs, err error) {

	// The next token that we expect to see is a
	// GROUP token, and if we don't find one then
	// return nil, with no error.

	if _, _, exi := p.mightBe(FETCH); !exi {
		return nil, nil
	}

	for {

		var tok Token
		var lit string

		one := &Fetch{}

		tok, lit, err = p.shouldBe(IDENT, EXPR)
		if err != nil {
			return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
		}

		one.Expr, err = p.declare(tok, lit)
		if err != nil {
			return nil, err
		}

		// Append the single expression to the array
		// of return statement expressions.

		mul = append(mul, one)

		// Check to see if the next token is a comma
		// and if not, then break out of the loop,
		// otherwise repeat until we find no comma.

		if _, _, exi := p.mightBe(COMMA); !exi {
			break
		}

	}

	return

}

func (p *parser) parseVersion() (Expr, error) {

	if _, _, exi := p.mightBe(VERSION, ON); !exi {
		return nil, nil
	}

	return p.parseExpr()

}