#include "stdafx.h"
#include "Schema.h"
#include "Core/Join.h"

namespace sql {

	Schema::Schema(Str *tableName, Array<Column *> *columns)
		: tableName(tableName), columns(columns) {}

	Schema::Schema(Str *tableName, Array<Column *> *columns, Array<Index *> *indices)
		: tableName(tableName), columns(columns), index(indices) {}

	Array<Schema::Index *> *Schema::indices() const {
		if (index)
			return index;
		else
			return new (this) Array<Index *>();
	}

	void Schema::toS(StrBuf *to) const {
		*to << tableName << S(": {\n");
		to->indent();
		for (Nat i = 0; i < columns->count(); i++) {
			*to << columns->at(i) << S("\n");
		}
		if (index) {
			for (Nat i = 0; i < index->count(); i++) {
				*to << index->at(i) << S("\n");
			}
		}
		to->dedent();
		*to << S("}");
	}

	void Schema::toSQL(QueryStrBuilder *to) const {
		to->put(S("CREATE TABLE "));
		to->name(tableName);
		to->put(S(" ("));

		Array<Str *> *pk = new (this) Array<Str *>();
		for (Nat i = 0; i < columns->count(); i++) {
			Column *col = columns->at(i);
			if (col->attributes & primaryKey)
				pk->push(col->name);
		}

		for (Nat i = 0; i < columns->count(); i++) {
			if (i > 0)
				to->put(S(", "));

			Column *col = columns->at(i);
			col->toSQL(to);

			// We need to supply PK info here for implicit autoincrement to work on some databases (e.g. SQLite).
			if (pk->count() == 1 && (col->attributes & primaryKey))
				to->put(S(" PRIMARY KEY"));
		}

		// Multiple primary keys go at the end.
		if (pk->count() > 1) {
			to->put(S(", PRIMARY KEY("));
			to->name(pk->at(0));
			for (Nat i = 1; i < pk->count(); i++) {
				to->put(S(", "));
				to->name(pk->at(i));
			}
			to->put(S(")"));
		}

		to->put(S(")"));
	}

	Schema::Column::Column(Str *name, QueryType type)
		: name(name), type(type), attributes(none) {}

	Schema::Column::Column(Str *name, QueryType type, Attributes attrs)
		: name(name), type(type), attributes(attrs) {}

	void Schema::Column::toS(StrBuf *to) const {
		*to << name << S(" ") << type;
		sql::toSQL(to, attributes);

		if (defaultValue)
			*to << S(" DEFAULT ") << defaultValue;

		if (unknown)
			*to << S(" unknown: ") << unknown;
	}

	void Schema::Column::toSQL(QueryStrBuilder *to) const {
		to->name(name);
		to->put(S(" "));
		to->type(type);

		sql::toSQL(to, attributes & ~primaryKey);

		if (defaultValue) {
			to->put(S(" DEFAULT "));
			to->put(defaultValue);
		}
	}

	Schema::Index::Index(Str *name, Array<Str *> *columns)
		: name(name), columns(columns) {}

	void Schema::Index::toS(StrBuf *to) const {
		*to << S("INDEX ON ") << name << S("(") << join(columns, S(", ")) << S(")");
	}

	void Schema::Index::toSQL(QueryStrBuilder *to, Str *table) const {
		to->put(S("CREATE INDEX "));
		to->name(name);
		to->put(S(" ON "));
		to->name(table);
		to->put(S("("));
		for (Nat i = 0; i < columns->count(); i++) {
			if (i > 0)
				to->put(S(", "));
			to->name(columns->at(i));
		}
		to->put(S(")"));
	}

	void toSQL(QueryStrBuilder *to, Schema::Attributes attributes) {
		if (attributes & Schema::primaryKey)
			to->put(S(" PRIMARY KEY"));
		if (attributes & Schema::notNull)
			to->put(S(" NOT NULL"));
		if (attributes & Schema::unique)
			to->put(S(" UNIQUE"));
		if (attributes & Schema::autoIncrement) {
			to->put(S(" "));
			to->autoIncrement();
		}
	}

	void toSQL(StrBuf *to, Schema::Attributes attributes) {
		if (attributes & Schema::primaryKey)
			*to <<S (" PRIMARY KEY");
		if (attributes & Schema::notNull)
			*to << S(" NOT NULL");
		if (attributes & Schema::unique)
			*to << S(" UNIQUE");
		if (attributes & Schema::autoIncrement)
			*to << S(" AUTOINCREMENT");
	}

	Str *toSQL(EnginePtr e, Schema::Attributes attrs) {
		StrBuf *out = new (e.v) StrBuf();
		toSQL(out, attrs);
		return out->toS();
	}

}
