/* Copyright (C) 2003 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include <common/common.hpp>
#include <NdbMutex.h>
#include <common/DiagArea.hpp>
#include "HandleRoot.hpp"
#include "HandleEnv.hpp"
#include "HandleDbc.hpp"
#include "HandleStmt.hpp"
#include "HandleDesc.hpp"
#include "PoolNdb.hpp"

HandleRoot::HandleRoot() :
    m_attrArea(m_attrSpec)
{
    m_attrArea.setHandle(this);
    m_poolNdb = new PoolNdb();
}

HandleRoot::~HandleRoot()
{
}

#ifdef NDB_WIN32
static NdbMutex & root_mutex = * NdbMutex_Create();
#else
static NdbMutex root_mutex = NDB_MUTEX_INITIALIZER;
#endif

HandleRoot*
HandleRoot::instance()
{
    NdbMutex_Lock(&root_mutex);
    if (m_instance == 0)
	m_instance = new HandleRoot();
    NdbMutex_Unlock(&root_mutex);
    return m_instance;
}

void
HandleRoot::lockHandle()
{
    NdbMutex_Lock(&root_mutex);
}

void
HandleRoot::unlockHandle()
{
    NdbMutex_Unlock(&root_mutex);
}

// check and find handle types and handles

SQLSMALLINT
HandleRoot::findParentType(SQLSMALLINT childType)
{
    switch (childType) {
    case SQL_HANDLE_ENV:
	return SQL_HANDLE_ROOT;
    case SQL_HANDLE_DBC:
	return SQL_HANDLE_ENV;
    case SQL_HANDLE_STMT:
	return SQL_HANDLE_DBC;
    case SQL_HANDLE_DESC:
	return SQL_HANDLE_DBC;
    }
    return -1;
}

HandleBase*
HandleRoot::findBase(SQLSMALLINT handleType, void* pHandle)
{
    switch (handleType) {
    case SQL_HANDLE_ROOT:
	return getRoot();
    case SQL_HANDLE_ENV:
	return findEnv(pHandle);
    case SQL_HANDLE_DBC:
	return findDbc(pHandle);
    case SQL_HANDLE_STMT:
	return findStmt(pHandle);
    case SQL_HANDLE_DESC:
	return findDesc(pHandle);
    }
    return 0;
}

HandleEnv*
HandleRoot::findEnv(void* pHandle)
{
    lockHandle();
    ValidList::iterator i = m_validList.find(pHandle);
    if (i == m_validList.end() || (*i).second != SQL_HANDLE_ENV) {
	unlockHandle();
	return 0;
    }
    unlockHandle();
    ctx_assert(pHandle != 0);
    return static_cast<HandleEnv*>(pHandle);
}

HandleDbc*
HandleRoot::findDbc(void* pHandle)
{
    lockHandle();
    ValidList::iterator i = m_validList.find(pHandle);
    if (i == m_validList.end() || (*i).second != SQL_HANDLE_DBC) {
	unlockHandle();
	return 0;
    }
    unlockHandle();
    ctx_assert(pHandle != 0);
    return static_cast<HandleDbc*>(pHandle);
}

HandleStmt*
HandleRoot::findStmt(void* pHandle)
{
    lockHandle();
    ValidList::iterator i = m_validList.find(pHandle);
    if (i == m_validList.end() || (*i).second != SQL_HANDLE_STMT) {
	unlockHandle();
	return 0;
    }
    unlockHandle();
    ctx_assert(pHandle != 0);
    return static_cast<HandleStmt*>(pHandle);
}

HandleDesc*
HandleRoot::findDesc(void* pHandle)
{
    lockHandle();
    ValidList::iterator i = m_validList.find(pHandle);
    if (i == m_validList.end() || (*i).second != SQL_HANDLE_DESC) {
	unlockHandle();
	return 0;
    }
    unlockHandle();
    ctx_assert(pHandle != 0);
    return static_cast<HandleDesc*>(pHandle);
}

// add or remove handle from validation list

void
HandleRoot::record(SQLSMALLINT handleType, HandleBase* pHandle, bool add)
{
    switch (handleType) {
    case SQL_HANDLE_ENV:
    case SQL_HANDLE_DBC:
    case SQL_HANDLE_STMT:
    case SQL_HANDLE_DESC:
	break;
    default:
	ctx_assert(false);
	break;
    }
    ctx_assert(pHandle != 0);
    lockHandle();
    ValidList::iterator i = m_validList.find(pHandle);
    if (add) {
	if (i != m_validList.end()) {
	    unlockHandle();
	    ctx_assert(false);
	}
	m_validList.insert(ValidList::value_type(pHandle, handleType));
    } else {
	if (i == m_validList.end() || (*i).second != handleType) {
	    unlockHandle();
	    ctx_assert(false);
	}
	m_validList.erase(i);
    }
    unlockHandle();
}

// allocate and free handles

void
HandleRoot::sqlAllocEnv(Ctx& ctx, HandleEnv** ppEnv)
{
    if (ppEnv == 0) {
	ctx.pushStatus(Sqlstate::_HY009, Error::Gen, "cannot allocate environment handle - null return address");
	return;
    }
    HandleEnv* pEnv = new HandleEnv(this);
    pEnv->ctor(ctx);
    if (! ctx.ok()) {
	pEnv->dtor(ctx);
	delete pEnv;
	return;
    }
    lockHandle();
    m_listEnv.push_back(pEnv);
    unlockHandle();
    getRoot()->record(SQL_HANDLE_ENV, pEnv, true);
    *ppEnv = pEnv;
}

void
HandleRoot::sqlAllocHandle(Ctx& ctx, SQLSMALLINT childType, HandleBase** ppChild)
{
    switch (childType) {
    case SQL_HANDLE_ENV:
	sqlAllocEnv(ctx, (HandleEnv**)ppChild);
	return;
    }
    ctx.pushStatus(Sqlstate::_HY092, Error::Gen, "invalid child handle type %d", (int)childType);
}

void
HandleRoot::sqlFreeEnv(Ctx& ctx, HandleEnv* pEnv)
{
    pEnv->dtor(ctx);
    if (! ctx.ok()) {
	return;
    }
    lockHandle();
    m_listEnv.remove(pEnv);
    unlockHandle();
    getRoot()->record(SQL_HANDLE_ENV, pEnv, false);
    delete pEnv;
}

void
HandleRoot::sqlFreeHandle(Ctx& ctx, SQLSMALLINT childType, HandleBase* pChild)
{
    switch (childType) {
    case SQL_HANDLE_ENV:
	sqlFreeEnv(ctx, (HandleEnv*)pChild);
	return;
    }
    ctx.pushStatus(Sqlstate::_HY092, Error::Gen, "invalid child handle type %d", (int)childType);
}

// process-level attributes

void
HandleRoot::sqlSetRootAttr(Ctx& ctx, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength)
{
    lockHandle();
    baseSetHandleAttr(ctx, m_attrArea, attribute, value, stringLength);
    unlockHandle();
}

void
HandleRoot::sqlGetRootAttr(Ctx& ctx, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER bufferLength, SQLINTEGER* stringLength)
{
    lockHandle();
    baseGetHandleAttr(ctx, m_attrArea, attribute, value, bufferLength, stringLength);
    unlockHandle();
}

// the instance

HandleRoot* HandleRoot::m_instance = 0;