/* $Id$ 
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more 
 * contributor license agreements. See the NOTICE file distributed with  
 * this work for additional information regarding copyright ownership. 
 * The ASF licenses this file to you under the Apache License, Version  
 * 2.0 (the "License"); you may not use this file except in compliance  
 * with the License. You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 

/*
 * etchexcp.c -- exception objects native and boxed  
 */

#include <stdio.h>
#include "etch_encoding.h"
#include "etchexcp.h"
#include "etch_global.h"
#pragma warning (disable:4996)


/**
 * etch_throw()
 * "throw" specified exception on specified object, meaning create an exception 
 * and store it in the specified object's return value.
 * type: arbitrary exception type, not edited. if a known type and text is not 
 * supplied, attempt is made to locate and supply default text for that type.
 * text: optional text descriptive of the exception.
 * bitwise flags: 
 * ETCHEXCP_COPYTEXT: make a copy of supplied text and free it on destroy
 * ETCHEXCP_FREETEXT: at destroy time, free the exception text memory
 */
objmask* etch_throw(objmask* obj, const excptype type, wchar_t* text, unsigned flags)
{
    int result = 0;
    if (!obj) return NULL;

    if(!obj->result)
        obj->result = new_etchresult(0, 0);

    if (obj->result->exception)
        destroy_exception(obj->result->exception);

    obj->result->exception = new_exception(type, text, flags);
    return obj;
} 


/**
 * etch_throwex()
 * throw specified exception.
 * same as etch_throw but with etch_result parameters specified as well.
 */
objmask* etch_throwex(objmask* obj, const excptype type, wchar_t* text,  
    unsigned flags, const int resultcode, const int reasoncode) 
{
    if (NULL == etch_throw(obj, type, text, flags)) return NULL;
    obj->result->resultcode = resultcode;
    obj->result->reasoncode = reasoncode;
    return obj;
} 


/*
 * new_exception() 
 * instantiate a native exception object.
 * this is not an etch object, it is a raw struct, see new_etch_exception()
 * bitwise flags: 
 *   ETCHEXCP_COPYTEXT:  make a copy of supplied text and free it on destroy
 *   ETCHEXCP_FREETEXT:  at destroy time, free the exception text memory
 *   ETCHEXCP_UNCHECKED: exception is unchecked
 */
etchexception* new_exception(const excptype type, wchar_t* text, const unsigned flags)
{
    size_t numchars = 0;
    etchexception* excp = etch_malloc(sizeof(etchexception), ETCHTYPEB_EXCEPTION);
    memset(excp, 0, sizeof(etchexception));
    excp->excptype = type;
    excp->flags    = flags;

    if (text && !flags) /* if text without flags, default to copy text */
        excp->flags = ETCHEXCP_COPYTEXT;
    else
    if(!text) /* if no text, get default text and clear copy and free flags */
    {   text = default_excptext(type);
        excp->flags &= ~(ETCHEXCP_FREETEXT | ETCHEXCP_COPYTEXT);  
    }

    if (text) /* calculate byte length of text */
    {   numchars = wcslen(text) + 1;
        excp->textlen  = sizeof(wchar_t) * numchars;
    }
    
    if (text && (flags & ETCHEXCP_COPYTEXT))
    {   /* if asked to make a copy of the text, do so */
        excp->excptext = etch_malloc(excp->textlen, ETCHTYPEB_STRING);
        wcscpy_s(excp->excptext, numchars, text); 
        excp->flags |= ETCHEXCP_FREETEXT; /* indicate free on destroy */
    }    
    else excp->excptext = text; /* ETCHEXCP_FREETEXT may or may not be set */

    /* etch_malloc an ansi copy for display */
    if (0 == etch_unicode_to_ansi (&excp->ansitext, excp->excptext))
        excp->atxtlen = strlen(excp->ansitext);

    if (type >= EXCPTYPE_CHECKED_START)  /* mark checked excp as such */
        excp->flags |= ETCHEXCP_CHECKED;
        
    return excp;
}


/**
 * destroy_etch_exception()
 */
int destroy_etch_exception(etch_exception* ee)
{   
    if (ee->refcount > 0 && --ee->refcount > 0) return -1;  

    if (!is_etchobj_static_content(ee))
        destroy_exception(ee->value);

    return destroy_objectex((objmask*) ee);
}


/**
 * clone_etch_exception()
 */
etch_exception* clone_etch_exception (etch_exception* ee)
{   
    etch_exception* newobj = NULL;
    etchexception* excp = ee? (etchexception*) ee->value: NULL;
    if (!excp) return NULL;
    newobj = new_etch_exception (excp->excptype, excp->excptext, ETCHEXCP_COPYTEXT);
    return newobj;
}


/**
 * new_etch_exception()
 * constructor for wrapped exception
 */ 
etch_exception* new_etch_exception(const excptype type, wchar_t* text, 
    const unsigned flags) 
{
    etch_exception* newobj = (etch_exception*) new_object
        (sizeof(struct etch_exception), ETCHTYPEB_EXCEPTION, CLASSID_EXCEPTION);

    newobj->value = new_exception(type, text, flags);

    newobj->destroy = destroy_etch_exception;
    newobj->clone   = clone_etch_exception;

    return newobj;
}


/* builtin exception text */
const wchar_t* excptext_excp    = L"etch"; 
const wchar_t* excptext_io      = L"I/O";
const wchar_t* excptext_auth    = L"authorization";
const wchar_t* excptext_runtime = L"etch runtime";
const wchar_t* excptext_nullptr = L"null pointer";
const wchar_t* excptext_arg     = L"illegal argument";  
const wchar_t* excptext_unsupop = L"unsupported operation"; 
const wchar_t* excptext_internal= L"internal error";
const wchar_t* excptext_notfound= L"not found";


/*
 * default_excptext() 
 * find and return default text for exception type
 */
wchar_t* default_excptext(const excptype type)
{
    wchar_t* text = NULL;

    switch(type)
    {   
       case EXCPTYPE_GENERIC:       text = (wchar_t*) excptext_excp;    break;  
       case EXCPTYPE_ETCHRUNTIME:   text = (wchar_t*) excptext_runtime; break;  
       case EXCPTYPE_ETCHAUTH:      text = (wchar_t*) excptext_auth;    break;   
       case EXCPTYPE_IO:            text = (wchar_t*) excptext_io;      break;   
       case EXCPTYPE_NULLPTR:       text = (wchar_t*) excptext_nullptr; break;    
       case EXCPTYPE_ILLEGALARG:    text = (wchar_t*) excptext_arg;     break; 
       case EXCPTYPE_UNSUPPORTEDOP: text = (wchar_t*) excptext_unsupop; break;
       case EXCPTYPE_INTERNALERR:   text = (wchar_t*) excptext_internal;break;
       case EXCPTYPE_NOTFOUND:      text = (wchar_t*) excptext_notfound;break;
    }

    return text;
}


/*
 * destroy_exception()
 * free memory for specified native exception object
 */
void destroy_exception (etchexception* excp)
{
    if(!excp) return;  /* text may be static or freed elsewhere */

    if (excp->flags & ETCHEXCP_FREETEXT)
    {   ETCHOBJ_FREE(excp->excptext);   /* free exception text */ 
    }

    /* always allocated in ctor, never passed in */
    ETCHOBJ_FREE(excp->ansitext);
   
    etch_free(excp);   /* free exception wrapper */
}


/*
 * get_excpobj()
 * create and return an etchobject for the sole purpose of returning an exception.
 * we might use this functionality where our method returns an object which we have
 * not yet created, we edit parameters, find them wanting, and wish to throw an 
 * exception, and need an object in which to return the exception.
 */ 
objmask* get_excpobj (int objsize, short obj_type, short class_id,
    excptype xcptype, wchar_t* xcptext, unsigned flags)
{
    objmask* newobj = new_object(objsize, obj_type, class_id);
    newobj->is_null = TRUE; 
    etch_throw(newobj, xcptype, xcptext, flags);
    return newobj;
} 


/*  
 * get_excptype()
 * return exception type given result code
 */
excptype get_excptype(const results result)
{
    excptype xtype;
    if (result >= 0) return EXCPTYPE_NONE;

    switch(result)
    {    
        case ETCHRESULT_EXCPERR_IO:      xtype = EXCPTYPE_IO;            break;
        case ETCHRESULT_EXCPERR_BADARG:  xtype = EXCPTYPE_ILLEGALARG;    break;
        case ETCHRESULT_EXCPERR_NULLPTR: xtype = EXCPTYPE_NULLPTR;       break;
        case ETCHRESULT_EXCPERR_BADOP:   xtype = EXCPTYPE_UNSUPPORTEDOP; break;
        default: xtype = EXCPTYPE_GENERIC;  
    }
    return xtype;
}


/*  
 * excp_tostring
 * returns unicode string representation of exception
 * caller owns returned buffer
 */
wchar_t* excp_tostring (etchexception* excp)
{
    wchar_t *newbuf = NULL;
    const wchar_t *x = L"exception ", charlen = sizeof(wchar_t), *space = L" " ;
    const size_t xbytelen = wcslen(x) * charlen;
    wchar_t* deftext = default_excptext(excp->excptype);
    const size_t deftextbytelen = deftext? 
          wcslen(deftext) * charlen: 0;
    const size_t excptextbytelen = excp->excptext? 
          wcslen(excp->excptext) * charlen: 0;
    wchar_t* excptext 
           = excptextbytelen? excp->excptext: 
             deftextbytelen?  deftext:
             L"generic";
    const size_t textbytelen = wcslen(excp->excptext) * charlen;
    const size_t buflen = xbytelen + textbytelen + (3 * charlen);

    newbuf = etch_malloc(buflen, ETCHTYPEB_BYTES);
    memset(newbuf, 0, buflen);
    
    wcscat(newbuf, excptext); /* e.g. "null pointer" */
    wcscat(newbuf, space);
    wcscat(newbuf, x);        /* "exception " */

    return newbuf; 
}


/*  
 * excp_tostringx
 * returns ascii string representation of exception
 * caller owns returned buffer
 */
char* excp_tostringx(etchexception* excp)
{
    char* ansibuf = NULL;
    wchar_t*  unibuf = excp_tostring(excp);
    const int result = etch_unicode_to_ansi(&ansibuf, unibuf); 
    etch_free(unibuf);
    return ansibuf; 
}


/*  
 * is_catch_exception()
 * "catch" exception defined by result code, returning FALSE if no exception.
 * the etchobject wrapping the exception is returned in the out parameter. 
 * this object is created if no such object is passed.
 */
boolean is_catch_exception(objmask* obj, const int result, objmask** outobj)
{
    objmask* excpobj = throw_exception(obj, result);

    if (is_exception(excpobj))
    {
        if (outobj) *outobj = excpobj;
        return TRUE;
    }

    return FALSE;
}


/*  
 * is_catch()
 * "catch" exception defined by result code, returning FALSE if no exception.
 * the etchobject wrapping the exception is returned in the out parameter. 
 * this object is created if no such object is passed.
 */
boolean is_catch(objmask* obj, const int result, const int objtype, objmask** outobj)
{
    objmask* excpobj = obj? 
        throw_exception(obj, result):
        throw_exception_from(objtype, result);

    if (is_exception(excpobj))
    {
        if (outobj) *outobj = excpobj;
        return TRUE;
    }

    return FALSE;
}


/*  
 * catch_exception()
 * return an etchobject containing the exception mapping to the specified 
 * result code. an anonymous object is generated if none is supplied.
 */
objmask* catch_exception(objmask* obj, const int result)
{
    return throw_exception(obj, result);
}


/*  
 * throw_exception()
 * return an etchobject of the specified type containing the exception mapping to
 * the specified result code. an anonymous object is generated if none is supplied.
 */
objmask* throw_exception(objmask* obj, const int result)
{
    excptype xtype = get_excptype(result);
    if (obj == NULL) 
        obj = get_excpobj(sizeof(objmask), ETCHTYPEB_UNDEFINED, 0, xtype, NULL, 0);

    if (xtype != EXCPTYPE_NONE)  
        etch_throw(obj, xtype, NULL, 0);

    return obj;
}


/*  
 * throw_exception_from()
 * generate and return an etch object of the specified type containing the exception
 * mapping to the specified result code.  
 */
objmask* throw_exception_from(const short obj_type, const int result)
{
    objmask* obj = NULL;
    excptype xtype  = get_excptype(result);
    obj = get_excpobj(sizeof(objmask), obj_type, 0, xtype, NULL, 0);
    return obj;
}


/*  
 * throw_from()
 * generate and return an etchobject of the specified type 
 * containing the specified exception
 */
objmask* throw_from(excptype xtype, const short obj_type, wchar_t* text, unsigned flags)
{
    return get_excpobj(sizeof(objmask), obj_type, 0, xtype, text, flags);
}


/*  
 * etch_rethrow
 * throw an exception from an existing exception 
 */
objmask* etch_rethrow(objmask* target, objmask* source)
{
    etchexception* excp = NULL;
    if (target != NULL)
        excp = get_exception(source);
    if (excp == NULL) return throw_from(EXCPTYPE_INTERNALERR, target->obj_type, NULL, 0);
    
    return etch_throwex(target, excp->excptype, excp->excptext, excp->flags, 
                source->result->resultcode, source->result->reasoncode);     
}


/*  
 * get_etch_exception_from()
 * returns the etch_exception object if any from the specified etch object.
 * @return a non-disposable reference to the etch_exception object, or NULL.
 */
etch_exception* get_etch_exception_from (objmask* obj)
{   
    if (obj && (obj->obj_type == ETCHTYPEB_EXCEPTION)) 
        return (etch_exception*) obj;
    return NULL;
}


/*  
 * get_exception_from
 * returns the unwrapped etchexception if any from the specified etch object.
 */
etchexception* get_exception_from (objmask* obj)
{   
    if (NULL == obj) return NULL;
    if (obj->obj_type == ETCHTYPEB_EXCEPTION) 
        return (etchexception*) ((etch_exception*) obj)->value;
    if (obj->result) return obj->result->exception;
    return NULL;
}


/*  
 * get_exception_from()
 * returns a disposable exception object based on the exception contained in
 * the specified object.
 */
etch_exception* new_etch_exception_from (objmask* obj)
{
    etch_exception* x = NULL;
    etchexception* excp = get_exception_from (obj);
    if (excp)
        x = new_etch_exception (excp->excptype, excp->excptext, ETCHEXCP_COPYTEXT);
    return x;
}


/*  
 * verifyx()
 * test specified object for specified type, throw specified exception if not
 */
objmask* verifyx(objmask* obj, 
    const unsigned short objtype, const unsigned short classid, excptype xtype)
{
    if(!obj)
        obj = throw_from(xtype, objtype, NULL, 0);
    else
    if ((objtype != 0 && obj->obj_type != objtype)
     || (classid != 0 && obj->class_id != classid)) 
        etch_throwex(obj, xtype, NULL, 0, -1, 0);

    return obj;
}









