/****************************************************************************
**
** Copyright (c) 2008-2011 C.B. Barber. All rights reserved.
** $Id: //main/2011/qhull/src/libqhullcpp/UsingLibQhull.cpp#3 $$Change: 1342 $
** $DateTime: 2011/03/07 21:55:47 $$Author: bbarber $
**
****************************************************************************/

#//! UsingLibQhull -- Set up qhull C code from C++

#include "Qhull.h"
#include "UsingLibQhull.h"
#include "QhullError.h"
#include "QhullQh.h"

#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
#endif

namespace orgQhull {

#//Class objects

const double UsingLibQhull::
DEFAULTdistanceEpsilon= 1e-15*FACTORepsilon; //! ~DISTround*FACTORepsilon for unit cube

const double UsingLibQhull::
DEFAULTangleEpsilon= 1e-15*FACTORepsilon; //! ~ANGLEround*FACTORepsilon for unit cube

    //! Global pointer to Qhull for qh_fprintf callback and QhullError
Qhull *
s_qhull_output= 0;

double UsingLibQhull::
s_angle_epsilon= 0;

double UsingLibQhull::
s_distance_epsilon= 0;

//! For QhullPoint.id() w/o qhRunId.  Initialized by Qhull
const coordT *UsingLibQhull::
s_points_begin= 0;
const coordT *UsingLibQhull::
s_points_end= 0;
int UsingLibQhull::
s_points_dimension= 0;

int UsingLibQhull::
s_vertex_dimension= 0;  // FIXUP QH11023: s_vertex_dimension is required if dimension>15.  Cannot store in QhullVertex

bool UsingLibQhull::
s_has_points= false;

bool UsingLibQhull::
s_has_angle_epsilon= false;

bool UsingLibQhull::
s_has_vertex_dimension= false;

bool UsingLibQhull::
s_has_distance_epsilon= false;

bool UsingLibQhull::
s_using_libqhull= false;

#//Constructors

//! Grabs global state (qh_qh, qh_qhstat, qhmem.tempstack)
//! Follow immediately with setjmp(qh errexit), otherwise errors in libqhull are not caught properly
//! See qh_restore_qhull [global.c]
UsingLibQhull::
UsingLibQhull(Qhull *q)
: my_qhull(q)
, qh_exitcode(0)
{
    checkUsingLibQhull();
    QhullQh *qhullqh= q->qhullQh();
    if(!qhullqh){
        throw QhullError(10014, "Qhull internal error: Qhull.qhullQh() not defined. initializeQhull() not called.");
    }
    if(qhullqh->run_id != q->qhull_run_id){
        throw QhullError(10015, "Qhull error: QhullQh.runId %d != Qhull.runId %d.  Overwritten?", qhullqh->run_id, q->qhull_run_id);
    }
    // qh.old_qhstat is zero at initialization
    // qh.old_tempstack is zero when empty
    // QhullQh() and UsingLibQhull() are the same
#if qh_QHpointer
    if(qh_qh){
        qh old_qhstat= qh_qhstat;
        qh old_tempstack= qhmem.tempstack;
    }
    qh_qh= qhullqh;
    qh_qhstat= qhullqh->old_qhstat;
    qhmem.tempstack= qhullqh->old_tempstack;
    qhullqh->old_qhstat= 0;
    qhullqh->old_tempstack= 0;
#else
    #error FIXUP QH11024 static qh_qh not tested.  Delete the line to try.
    if(qhullqh!=&qh_qh){
        throw QhullError(10040, "Qhull internal error: Qhull.qhullQh() is not qh_qh (%x, static).  Overwrite?", 0,0,0.0, &qh_qh);
    }
#endif
    s_qhull_output= q;      // set s_qhull_output for qh_fprintf()
    qh NOerrexit= False;   // assumes setjmp called next
}//UsingLibQhull qhull

//! Same as UsingLibQhull but does not throw exceptions
//! !defined() on failure.  For use in destructors
UsingLibQhull::
UsingLibQhull(Qhull *q, int noThrow)
: my_qhull(0)  // Fail by default
, qh_exitcode(0)
{
    QHULL_UNUSED(noThrow);

    QhullQh *qhullqh= q->qhullQh();
    if(s_using_libqhull){
        QhullError e(10050, "Qhull error: UsingLibQhull already in use");
        e.logError();
    }else if(!qhullqh || qhullqh->run_id != q->qhull_run_id){
        QhullError e(10051, "Qhull error: Qhull.qhullQh (%x) undefined or QhullQh.runId %d != Qhull.runId %d.  Overwritten?", (qhullqh ? qhullqh->run_id : -1), q->qhull_run_id, 0.0, qhullqh);
        e.logError();
    }else{
        // qh.old_qhstat is zero at initialization
        // qh.old_tempstack is zero when empty
        // QhullQh() and UsingLibQhull() are the same
#if qh_QHpointer
        if(qh_qh){
            qh old_qhstat= qh_qhstat;
            qh old_tempstack= qhmem.tempstack;
        }
        qh_qh= qhullqh;
        qh_qhstat= qhullqh->old_qhstat;
        qhmem.tempstack= qhullqh->old_tempstack;
        qhullqh->old_qhstat= 0;
        qhullqh->old_tempstack= 0;
#endif
        my_qhull= q;
        s_qhull_output= q;          // set s_qhull_output for qh_fprintf()
        qh NOerrexit= False;   // assumes setjmp called next
    }
}//UsingLibQhull qhull noThrow

//! Reuses current global state (qh_qh) from prior UsingQhull
//! Errors if runId is not the same
UsingLibQhull::
UsingLibQhull(int qhRunId)
: my_qhull(0)
, qh_exitcode(0)
{
    checkUsingLibQhull();
#if qh_QHpointer
    if(!qh_qh || !qh_qhstat){
        throw QhullError(10024, "Qhull error: UsingLibQhull is not active (qh_qh %x or qh_qhstat is not defined)", 0,0,0.0, qh_qh);
    }
#endif
    if(qh run_id!=qhRunId){
        throw QhullError(10036, "Qhull error: qhRunId %d != qh_qh.runId %d.  Is another Qhull active?", qhRunId, qh run_id);
    }
    if(!s_qhull_output){
        throw QhullError(10037, "Qhull error: UsingLibQhull not active(s_qhull_output undefined).  Invoke UsingLibQhull before this call");
    }
    if(s_qhull_output->qhull_run_id!=qhRunId){
        throw QhullError(10046, "Qhull error: qhRunId %d != s_qhull_output.runId %d.  Is another Qhull active", qhRunId, s_qhull_output->qhull_run_id);
    }
    my_qhull= s_qhull_output;
    qh NOerrexit= False;   // assumes setjmp called next
}//UsingLibQhull runId

//Leaves libqhull active for runId access
UsingLibQhull::
~UsingLibQhull()
{
    QhullError e= checkRunId();
    if(e.isDefined()){
        e.logError();
    }else{
#if qh_QHpointer
        if(qh_qh){
            qh NOerrexit= true;
        }
#else
        qh NOerrexit= true;
#endif
    }
    s_using_libqhull= false;
}//~UsingLibQhull

#//Class methods

void UsingLibQhull::
checkQhullMemoryEmpty()
{
    int curlong, totlong, curshort, totshort, maxlong, totbuffer;
    // qh_memtotal does not error
    qh_memtotal(&curlong, &totlong, &curshort, &totshort, &maxlong, &totbuffer);
    if (curlong || totlong){
        throw QhullError(10026, "Qhull error: qhull did not free %d bytes of long memory (%d pieces).", totlong, curlong);
    }
    if (curshort || totshort){
        throw QhullError(10035, "Qhull error: qhull did not free %d bytes of short memory (%d pieces).", totshort, curshort);
    }
}//checkQhullMemoryEmpty

double UsingLibQhull::
currentAngleEpsilon()
{
    if(s_qhull_output && s_qhull_output->initialized()){
        return s_qhull_output->qhullQh()->ANGLEround*FACTORepsilon;
    }else if(s_has_angle_epsilon){
        return s_angle_epsilon;
    }
    return UsingLibQhull::DEFAULTangleEpsilon;
}//currentAngleEpsilon

double UsingLibQhull::
currentDistanceEpsilon()
{
    if(s_qhull_output && s_qhull_output->initialized()){
        return s_qhull_output->qhullQh()->DISTround*FACTORepsilon;
    }else if(s_has_distance_epsilon){
        return s_distance_epsilon;
    }
    return UsingLibQhull::DEFAULTdistanceEpsilon;
}//currentDistanceEpsilon

const coordT *UsingLibQhull::
currentPoints(int *dimension, const coordT **pointsEnd)
{
    if(s_qhull_output && s_qhull_output->initialized()){
        *dimension= qh hull_dim;
        *pointsEnd= qh first_point+qh num_points*qh hull_dim;
        return qh first_point;
    }else if(s_has_points){
        *dimension= s_points_dimension;
        *pointsEnd= s_points_end;
        return s_points_begin;
    }
    throw QhullError(10059, "Qhull error: missing definition for currentPoints().  Need currentQhull() or setGlobalDistanceEpsilon()");
}//currentPoints

Qhull &UsingLibQhull::
currentQhull()
{
    if(!s_qhull_output){
        throw QhullError(10055, "Qhull error: currentQhull not defined.  Run qhull first.");
    }
    return *s_qhull_output;
}//currentQhull

// for QhullVertex::dimension() when >= 16
int UsingLibQhull::
currentVertexDimension()
{
    if(s_qhull_output && s_qhull_output->initialized()){
        return s_qhull_output->dimension();
    }else if(s_has_vertex_dimension){
        return s_vertex_dimension;
    }
    throw QhullError(10057, "Qhull error: missing definition for currentVertexDimension().  Need currentQhull() or setGlobalVertexDimension()");
}//currentVertexDimension

const coordT *UsingLibQhull::
globalPoints(int *dimension, const coordT **pointsEnd)
{
    if(s_has_points){
        *dimension= s_points_dimension;
        *pointsEnd= s_points_end;
        return s_points_begin;
    }else{
        return currentPoints(dimension, pointsEnd);
    }
}//globalPoints

bool UsingLibQhull::
hasPoints()
{
    return s_has_points || (s_qhull_output && s_qhull_output->initialized());
}

bool UsingLibQhull::
hasVertexDimension()
{
    return s_has_vertex_dimension || (s_qhull_output && s_qhull_output->initialized());
}

void UsingLibQhull::
setGlobals()
{
    if(s_qhull_output && s_qhull_output->initialized()){
        QhullQh *qqh= s_qhull_output->qhullQh();
        s_angle_epsilon= qqh->ANGLEround*FACTORepsilon;
        s_distance_epsilon= qqh->DISTround*FACTORepsilon;
        s_points_begin= qqh->first_point;
        s_points_dimension= qqh->hull_dim;
        s_points_end= s_points_begin+qqh->num_points*s_points_dimension;
        s_vertex_dimension= qqh->hull_dim;
        s_has_angle_epsilon= true;
        s_has_distance_epsilon= true;
        s_has_points= true;
        s_has_vertex_dimension= true;
    }else{
        throw QhullError(10058, "Qhull error: setGlobals can only be called for currentQhull().  Run qhull first.");
    }
 }//setGlobals

void UsingLibQhull::
unsetGlobals()
{
    s_has_angle_epsilon= false;
    s_has_distance_epsilon= false;
    s_has_points= false;
    s_has_vertex_dimension= false;
}//unsetGlobals

#// Methods

void UsingLibQhull::
maybeThrowQhullMessage(int exitCode) const
{
    my_qhull->maybeThrowQhullMessage(exitCode);
    QhullError e= checkRunId(); // Check for qhRunId after libqhull returns. For convenience, ought to be at end of libqhull try block
    if(e.isDefined()){
        throw e;
    }
}//maybeThrowQhullMessage

void UsingLibQhull::
maybeThrowQhullMessage(int exitCode, int noThrow) const
{
    my_qhull->maybeThrowQhullMessage(exitCode, noThrow);
    QhullError e= checkRunId(); // Check for qhRunId after libqhull returns. For convenience, ought to be at end of libqhull try block
    if(e.isDefined()){
        e.logError();
    }
}//maybeThrowQhullMessage

#//Helpers

//! Return QhullError for maybeThrowFromDestructor()
QhullError UsingLibQhull::
checkRunId() const
{
    // Predeclaring QhullError results in four copy constructors, none used here
#if qh_QHpointer
    if(qh_qh){ // 0 if ~Qhull
        if(my_qhull->qhull_run_id!=qh run_id){
            return QhullError(10047, "Qhull internal error: Global state (qh_qh, run_id %d) changed.  Should be runId %d.  Another thread?", qh run_id, my_qhull->qhull_run_id);
        }
    }
#else
    if(qh run_id!=0 && my_qhull->qhull_run_id!=qh run_id){
        return QhullError(10048, "Qhull internal error: Global state (qh_qh, run_id %d) changed.  Should be runId %d.  Another thread?", qh run_id, my_qhull->qhull_run_id);
    }
#endif
    return QhullError();
}//checkRunId

//! Can not embed UsingLibQhull.  Otherwise allocated a C++ object missed by qh_errexit
void UsingLibQhull::
checkUsingLibQhull() const
{
    if(s_using_libqhull){
        if(s_qhull_output){
            throw QhullError(10049, "Qhull error: UsingLibQhull already in use by QhullQh.runId %d", s_qhull_output->qhull_run_id);
        }else{
            throw QhullError(10050, "Qhull error: UsingLibQhull already in use.  No s_qhull_output");
        }
    }
    s_using_libqhull= true;
}//checkUsingLibQhull

}//namespace orgQhull

