////////////////////////////////////////////////////////
//
// GEM - Graphics Environment for Multimedia
//
// mark@danks.org
//
// Implementation file
//
//    Copyright (c) 1997-1999 Mark Danks.
//    For information on usage and redistribution, and for a DISCLAIMER OF ALL
//    WARRANTIES, see the file, "GEM.LICENSE.TERMS" in this distribution.
//
/////////////////////////////////////////////////////////

#include "multimodel.h"

#include <stdio.h>

#include "td.h"

CPPEXTERN_NEW_WITH_FOUR_ARGS(multimodel, t_symbol *, A_DEFSYM, t_floatarg, A_DEFFLOAT, t_floatarg, A_DEFFLOAT, t_floatarg, A_DEFFLOAT)

multimodel::multiModelCache *multimodel::s_modelCache = NULL;

/////////////////////////////////////////////////////////
//
// multimodel
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
multimodel :: multimodel(t_symbol *filename, t_floatarg baseModel,
                            t_floatarg topModel, t_floatarg skipRate)
            : m_loadedCache(NULL), m_numModels(0), m_curModel(-1), m_rescaleModel(1)
{
    inlet_new(this->x_obj, &this->x_obj->ob_pd, &s_float, gensym("mdl_num"));

    // make sure that there are some characters
    if (filename->s_name[0])
    { 
        if (skipRate == 0)
        {
            if (topModel == 0)
                openMess(filename, 0, (int)baseModel, 1);
            else
                openMess(filename, (int)baseModel, (int)topModel, 1);
        }
        else openMess(filename, (int)baseModel, (int)topModel, (int)skipRate);
    }
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
multimodel :: ~multimodel()
{
    cleanMultimodel();
}

/////////////////////////////////////////////////////////
// cleanMultimodel
//
/////////////////////////////////////////////////////////
void multimodel :: cleanMultimodel()
{
    if (m_numModels)
    {
        // decrement the reference count
        m_loadedCache->refCount--;

        // If the refCount == 0, then destroy the cache
        if (m_loadedCache->refCount == 0)
        {
            // find the cache
            multiModelCache *ptr = s_modelCache;
            
            // if the loaded cache is the first cache in the list
            if (m_loadedCache == s_modelCache)
            {
                s_modelCache = m_loadedCache->next;
                delete m_loadedCache;
            }
            else
            {
                while (ptr && ptr->next != m_loadedCache) ptr = ptr->next;
                if (!ptr) error("GEM: multimodel: Unable to find model cache!");
                else
                {
                    ptr->next = m_loadedCache->next;
                    delete m_loadedCache;
                }
            }
        }

	    m_loadedCache = NULL;
    	m_numModels = 0;
    }
}

/////////////////////////////////////////////////////////
// openMess
//
/////////////////////////////////////////////////////////
void multimodel :: openMess(t_symbol *filename, int baseModel, int topModel, int skipRate)
{
    cleanMultimodel();
    
    if (!topModel)
    {
    	post("GEM: multimodel: requires an int for number of models");
        return;
    }
    if (baseModel > topModel)
    {
        post("GEM: multimodel: Top range less than base model");
        return;
    }
    if (skipRate < 1) skipRate = 1;

    // have we already loaded the image?
    multiModelCache *cache = s_modelCache;
    int found = 0;
    while (!found && cache)
    {
        if (baseModel == cache->baseModel &&
            topModel == cache->topModel &&
            skipRate == cache->skipRate &&
            !strcmp(filename->s_name, cache->modelName)) found = 1;
        else cache = cache->next;
    }
    
    // yep, we have it
    if (found)
    {
        m_loadedCache = cache;
        m_loadedCache->refCount++;
        m_curModel = 0;
        m_numModels = m_loadedCache->numModels;
        post("GEM: multimodel: loaded models: %s from %d to %d skipping %d",
                filename->s_name, baseModel, topModel, skipRate);
        return;
    }

    // nope, so create the new cache
    // find the * in the filename    
    char preName[256];
    char postName[256];
    
    int i = 0;
    char *strPtr = filename->s_name;
    while (strPtr[i] && strPtr[i] != '*')
    {
    	preName[i] = strPtr[i];
    	i++;
    }
    
    if (!strPtr[i])
    {
    	post("GEM: multimodel: Unable to find * in file name");
    	return;
    }

    preName[i] = '\0';    
    strcpy(postName, &(strPtr[i+1]));
    
    // need to figure out how many filenames there are to load
    m_numModels = (topModel + 1 - baseModel) / skipRate;

    // create the new cache
    multiModelCache *newCache = new multiModelCache(filename->s_name);
    newCache->models = new GLint[m_numModels];
    newCache->numModels = m_numModels;
    newCache->baseModel = baseModel;
    newCache->topModel = topModel;
    newCache->skipRate = skipRate;

    int realNum = baseModel;
    char bufName[MAXPDSTRING];
    canvas_makefilename(getCanvas(), preName, bufName, MAXPDSTRING);

    for (i = 0; i < m_numModels; i++, realNum += skipRate)
    {
        char newName[256];
	    sprintf(newName, "%s%d%s", bufName, realNum, postName);

        // read the object in
        TDobject *tdObject = tdReadObject(newName);

        if (!tdObject)
   	    {
            // a load failed, blow away the cache
            newCache->numModels = i;
            delete newCache;
    	    m_numModels = 0;
	        return;
	    }

        // set the size to -1 to 1
		if (m_rescaleModel)
			tdSize(tdObject, 0.0);

        // generate normals if the object doesn't have them.
        if (tdObject->num_normals == 0)
			tdGenSmoothNormals(tdObject);

        // generate texture vertices if the object doesn't have them
        if (tdObject->num_texvertices == 0)
			tdGenLinearTexvertices(tdObject);
    
		// build a display list
		tdGenTriangles(tdObject);
        newCache->models[i] = tdGenDList(tdObject, TD_TRIANGLES, TD_VERTEX | TD_NORMAL | TD_TEXVERTEX); 

        // nuke it, we don't need it anymore
        tdDeleteObject(tdObject);
    }
    m_curModel = 0;

    m_loadedCache = newCache;
    newCache->refCount++;

    // insert the cache at the end of the linked list
    multiModelCache *ptr = s_modelCache;
    
    if (!ptr) s_modelCache = newCache;
    else
    {
        while(ptr->next) ptr = ptr->next;
        ptr->next = newCache;
    }

    post("GEM: multimodel: loaded models: %s %s from %d to %d skipping %d",
                bufName, postName, baseModel, topModel, skipRate);
    this->setModified();
}

/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void multimodel :: render(GemState *)
{
    if (!m_numModels || !m_loadedCache) return;
    glCallList(m_loadedCache->models[m_curModel]);
}

/////////////////////////////////////////////////////////
// rescaleMess
//
/////////////////////////////////////////////////////////
void multimodel :: rescaleMess(int state)
{
	m_rescaleModel = state;
}

/////////////////////////////////////////////////////////
// changeModel
//
/////////////////////////////////////////////////////////
void multimodel :: changeModel(int modelNum)
{
    if (modelNum >= m_numModels)
    {
    	error("GEM: multimodel: selection number too high: %d (max num is %d)", modelNum, m_numModels);
    	return;
    }
    else if (modelNum < 0)
    {
        error("GEM: multimodel: selection number must be > 0");
        return;
    }
    m_curModel = modelNum;
    this->setModified();
}

/////////////////////////////////////////////////////////
// static member function
//
/////////////////////////////////////////////////////////
void multimodel :: obj_setupCallback(t_class *classPtr)
{
    class_addmethod(classPtr, (t_method)&multimodel::openMessCallback,
    	    gensym("open"), A_SYMBOL, A_FLOAT, A_DEFFLOAT, A_DEFFLOAT, A_NULL);
    class_addmethod(classPtr, (t_method)&multimodel::changeModelCallback,
    	    gensym("mdl_num"), A_FLOAT, A_NULL);
    class_addmethod(classPtr, (t_method)&multimodel::rescaleMessCallback,
    	    gensym("rescale"), A_FLOAT, A_NULL);
}
void multimodel :: openMessCallback(void *data, t_symbol *filename, t_floatarg baseModel,
                                        t_floatarg topModel, t_floatarg skipRate)
{
    if ((int)skipRate == 0)
    {
        if ((int)topModel == 0)
			GetMyClass(data)->openMess(filename, 0, (int)topModel, 0);
        else
			GetMyClass(data)->openMess(filename, (int)baseModel, (int)topModel, 0);
    }
    else
		GetMyClass(data)->openMess(filename, (int)baseModel, (int)topModel, (int)skipRate);
}
void multimodel :: changeModelCallback(void *data, t_floatarg modelNum)
{
    GetMyClass(data)->changeModel((int)modelNum);
}
void multimodel :: rescaleMessCallback(void *data, t_floatarg state)
{
    GetMyClass(data)->rescaleMess((int)state);
}

