//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// pgDatatype.cpp - PostgreSQL Datatypes
//
//////////////////////////////////////////////////////////////////////////


#include <wx/wx.h>

// App headers
#include "pgAdmin3.h"
#include "utils/misc.h"
#include "schema/pgDatatype.h"
#include "schema/pgDatabase.h"
#include "utils/pgDefs.h"


pgDatatype::pgDatatype(const wxString &nsp, const wxString &typname, bool isDup, long numdims, long typmod)
{
	needSchema = isDup;
	schema = nsp;

	// Above 7.4, format_type also sends the schema name if it's not included
	// in the search_path, so we need to skip it in the typname
	if (typname.Contains(schema + wxT("\".")))
		name = typname.Mid(schema.Len() + 3); // "+2" because of the two double quotes
	else if (typname.Contains(schema + wxT(".")))
		name = typname.Mid(schema.Len() + 1);
	else
		name = typname;

	if (name.StartsWith(wxT("_")))
	{
		if (!numdims)
			numdims = 1;
		name = name.Mid(1);
	}
	if (name.Right(2) == wxT("[]"))
	{
		if (!numdims)
			numdims = 1;
		name = name.Left(name.Len() - 2);
	}

	if (name.StartsWith(wxT("\"")) && name.EndsWith(wxT("\"")))
		name = name.Mid(1, name.Len() - 2);

	if (numdims > 0)
	{
		while (numdims--)
			array += wxT("[]");
	}

	if (typmod != -1)
	{
		length = wxT("(");
		if (name == wxT("numeric"))
		{
			len = (typmod - 4L) >> 16L;
			prec = (typmod - 4) & 0xffff;
			length += NumToStr(len);
			if (prec)
				length += wxT(",") + NumToStr(prec);
		}
		else if (name == wxT("time") || name == wxT("timetz")
		         || name == wxT("time without time zone") || name == wxT("time with time zone")
		         || name == wxT("timestamp") || name == wxT("timestamptz")
		         || name == wxT("timestamp without time zone") || name == wxT("timestamp with time zone")
		         || name == wxT("bit") || name == wxT("bit varying") || name == wxT("varbit"))
		{
			prec = 0;
			len = typmod;
			length += NumToStr(len);
		}
		else if (name == wxT("interval"))
		{
			prec = 0;
			len = (typmod & 0xffff);
			length += NumToStr(len);
		}
		else if (name == wxT("date"))
		{
			len = prec = 0;
			length = wxT(""); /* Clear Length */
		}
		else
		{
			prec = 0;
			len = typmod - 4L;
			length += NumToStr(len);
		}

		if (length.Length() > 0)
			length += wxT(")");
	}
	else
		len = prec = 0;
}

// Return the full name of the type, with dimension and array qualifiers
wxString pgDatatype::FullName() const
{
	if (name == wxT("char") && schema == wxT("pg_catalog"))
		return wxT("\"char\"") + array;
	else if (name == wxT("time with time zone"))
		return wxT("time") + length + wxT(" with time zone") + array;
	else if (name == wxT("time without time zone"))
		return wxT("time") + length + wxT(" without time zone") + array;
	else if (name == wxT("timestamp with time zone"))
		return wxT("timestamp") + length + wxT(" with time zone") + array;
	else if (name == wxT("timestamp without time zone"))
		return wxT("timestamp") + length + wxT(" without time zone") + array;
	else
		return name + length + array;
}

// Return the quoted full name of the type, with dimension and array qualifiers
wxString pgDatatype::QuotedFullName() const
{
	if (name == wxT("char") && schema == wxT("pg_catalog"))
		return wxT("\"char\"") + array;
	else if (name == wxT("time with time zone"))
		return wxT("time") + length + wxT(" with time zone") + array;
	else if (name == wxT("time without time zone"))
		return wxT("time") + length + wxT(" without time zone") + array;
	else if (name == wxT("timestamp with time zone"))
		return wxT("timestamp") + length + wxT(" with time zone") + array;
	else if (name == wxT("timestamp without time zone"))
		return wxT("timestamp") + length + wxT(" without time zone") + array;
	else
		return qtTypeIdent(name) + length + array;
}

wxString pgDatatype::GetSchemaPrefix(pgDatabase *db) const
{
	if (schema.IsEmpty() || (!db && schema == wxT("pg_catalog")))
		return wxEmptyString;

	if (needSchema)
		return schema + wxT(".");

	return db->GetSchemaPrefix(schema);
}


wxString pgDatatype::GetQuotedSchemaPrefix(pgDatabase *db) const
{
	wxString str = GetSchemaPrefix(db);
	if (!str.IsEmpty())
		return qtIdent(str.Left(str.Length() - 1)) + wxT(".");
	return str;
}


long pgDatatype::GetTypmod(const wxString &name, const wxString &len, const wxString &prec)
{
	if (len.IsEmpty())
		return -1;
	if (name == wxT("numeric"))
	{
		return (((long)StrToLong(len) << 16) + StrToLong(prec)) + 4;
	}
	else if (name == wxT("time") || name == wxT("timetz")
	         || name == wxT("time without time zone") || name == wxT("time with time zone")
	         || name == wxT("timestamp") || name == wxT("timestamptz")
	         || name == wxT("timestamp without time zone") || name == wxT("timestamp with time zone")
	         || name == wxT("interval")  || name == wxT("bit") || name == wxT("bit varying") || name == wxT("varbit"))
	{
		return StrToLong(len);
	}
	else
	{
		return StrToLong(len) + 4;
	}
}


DatatypeReader::DatatypeReader(pgDatabase *db, const wxString &condition, bool addSerials)
{
	init(db, condition, addSerials);
}


DatatypeReader::DatatypeReader(pgDatabase *db, bool withDomains, bool addSerials)
{
	wxString condition = wxT("typisdefined AND typtype ");
	// We don't get pseudotypes here
	if (withDomains)
		condition += wxT("IN ('b', 'c', 'd', 'e', 'r')");
	else
		condition += wxT("IN ('b', 'c', 'e', 'r')");

	condition += wxT("AND NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = typname and relkind != 'c') AND (typname not like '_%' OR NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = substring(typname from 2)::name and relkind != 'c')) ");

	if (!settings->GetShowSystemObjects())
		condition += wxT(" AND nsp.nspname != 'information_schema'");
	init(db, condition, addSerials);
}

void DatatypeReader::init(pgDatabase *db, const wxString &condition, bool addSerials)
{
	database = db;
	wxString sql = wxT("SELECT * FROM (SELECT format_type(t.oid,NULL) AS typname, CASE WHEN typelem > 0 THEN typelem ELSE t.oid END as elemoid, typlen, typtype, t.oid, nspname,\n")
	               wxT("       (SELECT COUNT(1) FROM pg_type t2 WHERE t2.typname = t.typname) > 1 AS isdup, c.relpartbound is not null or c.relkind in('i','I') AS ispart\n")
	               wxT("  FROM pg_type t\n")
	               wxT("  JOIN pg_namespace nsp ON typnamespace=nsp.oid left join pg_class c on c.oid=t.typrelid\n")
	               wxT(" WHERE (NOT (typname = 'unknown' AND nspname = 'pg_catalog')) AND ") + condition + wxT("\n");

	if (addSerials)
	{
		if (db->GetConnection()->BackendMinimumVersion(9, 2))
		{
			sql += wxT(" UNION SELECT 'smallserial', 0, 2, 'b', 0, 'pg_catalog', false, false\n");
		}
		sql += wxT(" UNION SELECT 'bigserial', 0, 8, 'b', 0, 'pg_catalog', false, false\n");
		sql += wxT(" UNION SELECT 'serial', 0, 4, 'b', 0, 'pg_catalog', false, false\n");
	}

	sql += wxT("  ) AS dummy ORDER BY nspname <> 'pg_catalog', nspname <> 'public', nspname, 1");

	set = db->GetConnection()->ExecuteSet(sql);
}


bool DatatypeReader::IsDomain() const
{
	return set->GetVal(wxT("typtype")) == 'd';
}

bool DatatypeReader::IsPartition() const
{
	return set->GetBool(wxT("ispart"));
}


bool DatatypeReader::IsVarlen() const
{
	return set->GetLong(wxT("typlen")) == -1;
}


bool DatatypeReader::MaySpecifyLength() const
{
	if (IsDomain())
		return false;

	switch ((long)set->GetOid(wxT("elemoid")))
	{
		case PGOID_TYPE_BIT:
		case PGOID_TYPE_CHAR:
		case PGOID_TYPE_VARCHAR:
		case PGOID_TYPE_NUMERIC:
			return true;
		default:
			return false;
	}
}


bool DatatypeReader::MaySpecifyPrecision() const
{
	if (IsDomain())
		return false;

	switch ((long)set->GetOid(wxT("elemoid")))
	{
		case PGOID_TYPE_NUMERIC:
			return true;
		default:
			return false;
	}
}


pgDatatype DatatypeReader::GetDatatype() const
{
	return pgDatatype(set->GetVal(wxT("nspname")), set->GetVal(wxT("typname")), set->GetBool(wxT("isdup")));
}


wxString DatatypeReader::GetTypename() const
{
	return set->GetVal(wxT("typname"));
}


wxString DatatypeReader::GetSchema() const
{
	return set->GetVal(wxT("nspname"));
}


wxString DatatypeReader::GetSchemaPrefix() const
{
	if (set->GetBool(wxT("isdup")))
		return set->GetVal(wxT("nspname")) + wxT(".");
	else
		return database->GetSchemaPrefix(set->GetVal(wxT("nspname")));
}


wxString DatatypeReader::GetQuotedSchemaPrefix() const
{
	if (set->GetBool(wxT("isdup")))
		return qtIdent(set->GetVal(wxT("nspname"))) + wxT(".");
	else
		return database->GetQuotedSchemaPrefix(set->GetVal(wxT("nspname")));
}


wxString DatatypeReader::GetOidStr() const
{
	return set->GetVal(wxT("oid"));
}


OID DatatypeReader::GetOid() const
{
	return set->GetOid(wxT("oid"));
}
