// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Package db provides a single interface for interacting with different data
// sources through the use of adapters that wrap well-known database drivers.
//
// As of today, `upper.io/db` fully supports MySQL, PostgreSQL and SQLite (CRUD
// + Transactions) and provides partial support for MongoDB and QL (CRUD only).
//
// Usage:
//
// 	import(
//		// Main package.
// 		"upper.io/db"
//		// PostgreSQL adapter.
// 		"upper.io/db/postgresql"
// 	)
//
// `upper.io/db` is not an ORM and thus does not impose any hard restrictions
// on data structures:
//
//	// This code works the same for all supported databases.
//	var people []Person
//	res = col.Find(db.Cond{"name": "Max"}).Limit(2).Sort("-input")
//	err = res.All(&people)
package db // import "upper.io/db"

import (
	"reflect"

	"upper.io/builder"
)

// Cond is a map that defines conditions that can be passed to
// `db.Collection.Find()` and `db.Result.Where()`.
//
// Each entry of the map represents a condition (a column-value relation bound
// by a comparison operator). The comparison operator is optional and can be
// specified after the column name, if no comparison operator is provided the
// equality is used.
//
// Examples:
//
//	// Where age equals 18.
//	db.Cond{"age": 18}
//
//	// Where age is greater than or equal to 18.
//	db.Cond{"age >=": 18}
//
//	// Where id is in a list of ids.
//	db.Cond{"id IN": []{1, 2, 3}}
//
//	// Where age is lower than 18 (mongodb-like operator).
//	db.Cond{"age $lt": 18}
//
//  // Where age > 32 and age < 35
//  db.Cond{"age >": 32, "age <": 35}
type Cond builder.M

// Constraints returns all the conditions on the map.
func (m Cond) Constraints() []builder.Constraint {
	return builder.M(m).Constraints()
}

// Operator returns the logical operator that joins the conditions (defaults to
// "AND").
func (m Cond) Operator() builder.CompoundOperator {
	return builder.M(m).Operator()
}

// Sentences returns the map as a compound, so it can be used with Or() and
// And().
func (m Cond) Sentences() []builder.Compound {
	return builder.M(m).Sentences()
}

// Func represents a database function.
//
// Examples:
//
//	// MOD(29, 9)
//	db.Func("MOD", 29, 9)
//
//	// CONCAT("foo", "bar")
//	db.Func("CONCAT", "foo", "bar")
//
//	// NOW()
//	db.Func("NOW")
//
//	// RTRIM("Hello  ")
//	db.Func("RTRIM", "Hello  ")
func Func(name string, args ...interface{}) builder.Function {
	if len(args) == 1 {
		if reflect.TypeOf(args[0]).Kind() == reflect.Slice {
			iargs := make([]interface{}, len(args))
			for i := range args {
				iargs[i] = args[i]
			}
			args = iargs
		}
	}
	return &dbFunc{name: name, args: args}
}

// And joins conditions under logical conjunction. Conditions can be
// represented by db.Cond{}, db.Or() or db.And().
//
// Examples:
//
//	// name = "Peter" AND last_name = "Parker"
//	db.And(
// 		db.Cond{"name": "Peter"},
// 		db.Cond{"last_name": "Parker "},
// 	)
//
//	// (name = "Peter" OR name = "Mickey") AND last_name = "Mouse"
// 	db.And(
// 		db.Or(
// 			db.Cond{"name": "Peter"},
// 			db.Cond{"name": "Mickey"},
// 		),
// 		db.Cond{"last_name": "Mouse"},
// 	)
var And = builder.And

// Or joins conditions under logical disjunction. Conditions can be represented
// by db.Cond{}, db.Or() or db.And().
//
// Example:
//
// 	// year = 2012 OR year = 1987
// 	db.Or(
// 		db.Cond{"year": 2012},
// 		db.Cond{"year": 1987},
// 	)
var Or = builder.Or

// Raw marks chunks of data as protected, so they pass directly to the query
// without any filtering. Use with care.
//
// Example:
//
//	// SOUNDEX('Hello')
//	Raw("SOUNDEX('Hello')")
var Raw = builder.Raw

// Database is an interface that defines methods that must be satisfied by
// database adapters.
type Database interface {
	// Driver returns the underlying driver the wrapper uses.
	//
	// In order to actually use the driver the `interface{}` value has to be
	// casted to the appropriate type.
	//
	// Example:
	//  internalSQLDriver := sess.Driver().(*sql.DB)
	Driver() interface{}

	// Builder returns a query builder that can be used to execute advanced
	// queries. Builder may not be defined for all database adapters, in that
	// case the return value would be nil.
	Builder() builder.Builder

	// Open attempts to stablish a connection with the database manager, a
	// previous call to `Setup()` is required.
	Open() error

	// Clone duplicates the current database session. Returns an error if the
	// clone did not succeed.
	Clone() (Database, error)

	// Ping returns an error if the database manager cannot be reached.
	Ping() error

	// Close closes the currently active connection to the database.
	Close() error

	// C is a short-hand for `Collection()`. If the given collection does not
	// exists subsequent calls to any `Collection{}` or `Result{}` method that
	// expect the collection to exists will fail returning the original error a
	// call to `Collection()` would have returned. The output of `C()` may be a
	// cached collection value.
	C(string) Collection

	// Collection returns a `Collection{}` given a table name.
	Collection(string) (Collection, error)

	// Collections returns the names of all non-system tables on the database.
	Collections() ([]string, error)

	// Use attempts to connect to another database using the same connection
	// settings.
	Use(string) error

	// Drop deletes all tables on the active database and drops the database.
	Drop() error

	// Setup stores database connection settings.
	Setup(ConnectionURL) error

	// Name returns the name of the active database.
	Name() string

	// Transaction starts a transaction block. Some databases do not support
	// transactions, refer to the documentation of the specific database adapter
	// to see the current status on transactions.
	Transaction() (Tx, error)
}

// Tx is an interface that enhaces the `Database` interface with additional
// methods for transactions.
//
// Example:
//	// [...]
// 	if sess, err = db.Open(postgresql.Adapter, settings); err != nil {
// 		log.Fatal(err)
// 	}
//
// 	var tx db.Tx
// 	if tx, err = sess.Transaction(); err != nil {
// 		log.Fatal(err)
// 	}
//
// 	var artist db.Collection
// 	if artist, err = tx.Collection("artist"); err != nil {
// 		log.Fatal(err)
// 	}
//	// [...]
type Tx interface {
	Database

	// Rollback discards all the instructions on the current transaction.
	Rollback() error

	// Commit commits the current transactions.
	Commit() error
}

// Collection is an interface that defines methods useful for handling data
// sources or tables.
type Collection interface {

	// Append inserts a new item into the collection. Accepts a map or a struct
	// as argument.
	Append(interface{}) (interface{}, error)

	// Exists returns true if the collection exists.
	Exists() bool

	// Find returns a result set with the given filters.
	Find(...interface{}) Result

	// Truncate removes all elements on the collection and resets its IDs.
	Truncate() error

	// Name returns the name of the collection.
	Name() string
}

// Result is an interface that defines methods useful for working with result
// sets.
type Result interface {

	// Limit defines the maximum number of results in this set. It only has
	// effect on `One()`, `All()` and `Next()`.
	Limit(uint) Result

	// Skip ignores the first *n* results. It only has effect on `One()`, `All()`
	// and `Next()`.
	Skip(uint) Result

	// Sort receives field names that define the order in which elements will be
	// returned in a query, field names may be prefixed with a minus sign (-)
	// indicating descending order, ascending order will be used otherwise.
	Sort(...interface{}) Result

	// Select defines specific columns to be returned from the elements of the
	// set.
	Select(...interface{}) Result

	// Where discards the initial filtering conditions and sets new ones.
	Where(...interface{}) Result

	// Group is used to group results that have the same value in the same column
	// or columns.
	Group(...interface{}) Result

	// Remove deletes all items within the result set. `Skip()` and `Limit()` are
	// not honoured by `Remove()`.
	Remove() error

	// Update modifies all items within the result set. `Skip()` and `Limit()`
	// are not honoured by `Update()`.
	Update(interface{}) error

	// Count returns the number of items that match the set conditions. `Skip()`
	// and `Limit()` are not honoured by `Count()`
	Count() (uint64, error)

	// Next fetches the next result within the result set and dumps it into the
	// given pointer to struct or pointer to map. You must manually call
	// `Close()` after finishing using `Next()`.
	Next(interface{}) error

	// One fetches the first result within the result set and dumps it into the
	// given pointer to struct or pointer to map. The result set is automatically
	// closed after picking the element, so there is no need to call `Close()`
	// manually.
	One(interface{}) error

	// All fetches all results within the result set and dumps them into the
	// given pointer to slice of maps or structs.  The result set is
	// automatically closed, so there is no need to call `Close()` manually.
	All(interface{}) error

	// Close closes the result set.
	Close() error
}

// ConnectionURL represents a connection string
type ConnectionURL interface {
	// String returns the connection string that is going to be passed to the
	// adapter.
	String() string
}

// Marshaler is the interface implemented by structs that can marshal
// themselves into data suitable for storage.
type Marshaler builder.Marshaler

// Unmarshaler is the interface implemented by structs that can transform
// themselves from storage data into a valid value.
type Unmarshaler builder.Unmarshaler

// IDSetter defines methods to be implemented by structs tha can update their
// own IDs.
type IDSetter interface {
	SetID(map[string]interface{}) error
}

// Constrainer defined methods to be implemented by structs that can set its
// own constraints.
type Constrainer interface {
	Constraints() Cond
}

// Int64IDSetter defined methods to be implemented by structs that can update
// their own int64 ID.
type Int64IDSetter interface {
	SetID(int64) error
}

// Uint64IDSetter defined methods to be implemented by structs that can update
// their own uint64 ID.
type Uint64IDSetter interface {
	SetID(uint64) error
}

// EnvEnableDebug can be used by adapters to determine if the user has enabled
// debugging.
//
// If the user sets the `UPPERIO_DB_DEBUG` environment variable to a
// non-empty value, all generated statements will be printed at runtime to
// the standard logger.
//
// Example:
//
//	UPPERIO_DB_DEBUG=1 go test
//
//	UPPERIO_DB_DEBUG=1 ./go-program
const EnvEnableDebug = `UPPERIO_DB_DEBUG`

type dbFunc struct {
	name string
	args []interface{}
}

func (f *dbFunc) Arguments() []interface{} {
	return f.args
}

func (f *dbFunc) Name() string {
	return f.name
}

var (
	_ = builder.Constraints(Cond{})
	_ = builder.Compound(Cond{})

	_ = builder.Function(&dbFunc{})
)