package schema

import (
	"database/sql"
	"strings"
)

type dialect interface {
	escapeIdent(ident string) string

	ColumnTypes(db *sql.DB, schema, name string) ([]*sql.ColumnType, error)
	PrimaryKey(db *sql.DB, schema, name string) ([]string, error)
	TableNames(db *sql.DB) ([][2]string, error)
	ViewNames(db *sql.DB) ([][2]string, error)
}

// driverDialect is a registry, mapping database/sql driver names to database dialects.
// This is somewhat fragile.
var driverDialect = map[string]dialect{
	"*sqlite3.SQLiteDriver":        sqliteDialect{},   // github.com/mattn/go-sqlite3
	"*sqlite.impl":                 sqliteDialect{},   // github.com/gwenn/gosqlite
	"sqlite3.Driver":               sqliteDialect{},   // github.com/mxk/go-sqlite
	"*pq.Driver":                   postgresDialect{}, // github.com/lib/pq
	"*stdlib.Driver":               postgresDialect{}, // github.com/jackc/pgx
	"*pgsqldriver.postgresDriver":  postgresDialect{}, // github.com/jbarham/gopgsqldriver
	"*gosnowflake.SnowflakeDriver": postgresDialect{}, // github.com/snowflakedb/gosnowflake
	"*mysql.MySQLDriver":           mysqlDialect{},    // github.com/go-sql-driver/mysql
	"*godrv.Driver":                mysqlDialect{},    // github.com/ziutek/mymysql
	"vitessdriver.drv":             mysqlDialect{},    // github.com/vitessio/vitess
	"*mssql.Driver":                mssqlDialect{},    // github.com/denisenkom/go-mssqldb
	"*mssql.MssqlDriver":           mssqlDialect{},    // github.com/denisenkom/go-mssqldb
	"*freetds.MssqlDriver":         mssqlDialect{},    // github.com/minus5/gofreetds
	"*goracle.drv":                 oracleDialect{},   // gopkg.in/goracle.v2
	"*godror.drv":                  oracleDialect{},   // github.com/godror/godror
	"*ora.Drv":                     oracleDialect{},   // gopkg.in/rana/ora.v4
	"*oci8.OCI8DriverStruct":       oracleDialect{},   // github.com/mattn/go-oci8
	"*oci8.OCI8Driver":             oracleDialect{},   // github.com/mattn/go-oci8
}

// TODO Should we expose a method of registering a driver string/dialect in our registry?
// -- It would allow folk to work around the fragility. e.g.
//
// func Register(driver sql.Driver, d *Dialect) {}
//

// // pack a string, normalising its whitespace.
// func pack(s string) string {
// 	return strings.Join(strings.Fields(s), " ")
// }

// escapeWithDoubleQuotes implements double-quote escaping of a string,
// in accordance with SQL:1999 standard.
func escapeWithDoubleQuotes(s string) string {
	return escape(s, '"', '"')
}

// escapeWithBackticks implements backtick escaping of a string.
func escapeWithBackticks(s string) string {
	return escape(s, '`', '`')
}

// escapeWithBrackets implements bracket escaping of a string.
func escapeWithBrackets(s string) string {
	return escape(s, '[', ']')
}

// escapeWithBraces implements brace escaping of a string.
func escapeWithBraces(s string) string {
	return escape(s, '{', '}')
}

// escape escapes a string identifier.
func escape(s string, escBegin, escEnd byte) string {
	// It would be nice to know when not to escape,
	// but a regex (e.g. "^[a-zA-Z_][a-zA-Z0-9_#@$]*$")
	// doesn't solve this, because it would not catch keywords.
	// Which is why we simply always escape identifiers.

	// TODO(js) Correct handling of backslash escaping of identifiers needs
	// further investigation: different dialects look to handle it differently
	// - removed for now.
	// Please file an issue if you encounter a problem regarding backslash escaping.

	var b strings.Builder
	b.WriteByte(escBegin)
	for i := 0; i < len(s); i++ {
		c := s[i]
		b.WriteByte(c)
		if c == escEnd { // || c == '\\' {
			b.WriteByte(c)
		}
	}
	b.WriteByte(escEnd)
	return b.String()
}
