package jet

import (
	"errors"
	"github.com/go-jet/jet/internal/utils"
	"reflect"
	"strings"
)

func serializeOrderByClauseList(statement statementType, orderByClauses []orderByClause, out *sqlBuilder) error {

	for i, value := range orderByClauses {
		if i > 0 {
			out.writeString(", ")
		}

		err := value.serializeForOrderBy(statement, out)

		if err != nil {
			return err
		}
	}

	return nil
}

func serializeGroupByClauseList(statement statementType, clauses []groupByClause, out *sqlBuilder) (err error) {

	for i, c := range clauses {
		if i > 0 {
			out.writeString(", ")
		}

		if c == nil {
			return errors.New("jet: nil clause")
		}

		if err = c.serializeForGroupBy(statement, out); err != nil {
			return
		}
	}

	return nil
}

func serializeClauseList(statement statementType, clauses []clause, out *sqlBuilder) (err error) {

	for i, c := range clauses {
		if i > 0 {
			out.writeString(", ")
		}

		if c == nil {
			return errors.New("jet: nil clause")
		}

		if err = c.serialize(statement, out); err != nil {
			return
		}
	}

	return nil
}

func serializeExpressionList(statement statementType, expressions []Expression, separator string, out *sqlBuilder) error {

	for i, value := range expressions {
		if i > 0 {
			out.writeString(separator)
		}

		err := value.serialize(statement, out)

		if err != nil {
			return err
		}
	}

	return nil
}

func serializeProjectionList(statement statementType, projections []projection, out *sqlBuilder) error {
	for i, col := range projections {
		if i > 0 {
			out.writeString(",")
			out.newLine()
		}

		if col == nil {
			return errors.New("jet: projection is nil")
		}

		if err := col.serializeForProjection(statement, out); err != nil {
			return err
		}
	}

	return nil
}

func serializeColumnNames(columns []column, out *sqlBuilder) error {
	for i, col := range columns {
		if i > 0 {
			out.writeString(", ")
		}

		if col == nil {
			return errors.New("jet: nil column in columns list")
		}

		out.writeString(col.Name())
	}

	return nil
}

func columnListToProjectionList(columns []Column) []projection {
	var ret []projection

	for _, column := range columns {
		ret = append(ret, column)
	}

	return ret
}

func valueToClause(value interface{}) clause {
	if clause, ok := value.(clause); ok {
		return clause
	}

	return literal(value)
}

func unwindRowFromModel(columns []column, data interface{}) []clause {
	structValue := reflect.Indirect(reflect.ValueOf(data))

	row := []clause{}

	mustBe(structValue, reflect.Struct)

	for _, column := range columns {
		columnName := column.Name()
		structFieldName := utils.ToGoIdentifier(columnName)

		structField := structValue.FieldByName(structFieldName)

		if !structField.IsValid() {
			panic("missing struct field for column : " + column.Name())
		}

		var field interface{}

		if structField.Kind() == reflect.Ptr && structField.IsNil() {
			field = nil
		} else {
			field = reflect.Indirect(structField).Interface()
		}

		row = append(row, literal(field))
	}

	return row
}

func unwindRowsFromModels(columns []column, data interface{}) [][]clause {
	sliceValue := reflect.Indirect(reflect.ValueOf(data))
	mustBe(sliceValue, reflect.Slice)

	rows := [][]clause{}

	for i := 0; i < sliceValue.Len(); i++ {
		structValue := sliceValue.Index(i)

		rows = append(rows, unwindRowFromModel(columns, structValue.Interface()))
	}

	return rows
}

func unwindRowFromValues(value interface{}, values []interface{}) []clause {
	row := []clause{}

	allValues := append([]interface{}{value}, values...)

	for _, val := range allValues {
		row = append(row, valueToClause(val))
	}

	return row
}

func mustBe(v reflect.Value, expectedKinds ...reflect.Kind) {
	indirectV := reflect.Indirect(v)
	types := []string{}

	for _, expectedKind := range expectedKinds {
		types = append(types, expectedKind.String())
		if k := indirectV.Kind(); k == expectedKind {
			return
		}
	}

	panic("argument mismatch: expected " + strings.Join(types, " or ") + ", got " + v.Type().String())
}
