// 30 Oct 2002
// AC3D loader for models generated by the AC3D modeller (www.ac3d.org)
// part of this source code were supplied by the AC3D project (Andy Colebourne)
// eg the basic parsing of an AC3D file.
// Conversion from AC3D scenegraph to OSG by GW Michel.


#include <stdio.h>
#ifdef __DARWIN_OSX__
#include <sys/malloc.h>
#else
#include <malloc.h>
#endif
#include <math.h>

#include <osg/CullFace>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>  //Set>
#include <osg/Light>
#include <osg/LightSource>
#include <osg/Material>
#include <osg/Texture2D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/Notify>
#include <osg/Texture2D>


#include <osgDB/FileNameUtils>
#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>

#include <osgUtil/Tesselator>
#include <osgUtil/SmoothingVisitor>

#include "osgac3d.h"

class ReaderWriterAC : public osgDB::ReaderWriter
{
    public:
        virtual const char* className() { return "AC3D Database Reader"; }

        virtual bool acceptsExtension(const std::string& extension)
        {
            return osgDB::equalCaseInsensitive(extension,"ac");
        }
        virtual ReadResult readNode(const std::string& fileName,const osgDB::ReaderWriter::Options*)
        {
            osg::Group *grp; // holder for all loaded objects
            grp=ac_load_ac3d(fileName.c_str());
            return grp;
        };
private:
};

static int line = 0;
static char buff[255];



static osg::Material *palette[255];
static int num_palette = 0;
static int startmatindex = 0;


osg::Material*ac_palette_get_material(int id)
{
    return(palette[id]);
}


Boolean read_line(FILE *f)
{
    fgets(buff, 255, f); line++;
    return(TRUE);
}



int tokc = 0;
char *tokv[30];

Prototype int get_tokens(char *s, int *argc, char *argv[])
/** bung '\0' chars at the end of tokens and set up the array (tokv) and count (tokc)
    like argv argc **/
{
char *p = s;
char *st;
char c;
//int n;
int tc;

    tc = 0;
    while ((c = *p))
        {
        if ((c != ' ') && (c != '\t') && (c != '\n') && ( c != 13))
            {
            if (c == '"')
                {
                c = *p++;
                st = p;
                while ((c = *p) && ((c != '"')&&(c != '\n')&& ( c != 13)) )
                    {
                    if (c == '\\')
                       strcpy(p, p+1);
                    p++;
                    }
                *p=0;
                argv[tc++] = st;
                }
            else
                {
                st = p;
                while ((c = *p) && ((c != ' ') && (c != '\t') && (c != '\n') && ( c != 13)) )
                        p++;
                *p=0;
                argv[tc++] = st;
                }            
            }
        p++;
        }

    *argc = tc;
    return(tc);
}


void initobject(ACObject *ob)
{
    ob->loc[0] = ob->loc[1] = ob->loc[2] = 0.0;
    ob->name = ob->url = NULL;
    ob->data = NULL;
    ob->num_vert = 0;
    ob->num_surf = 0;
    ob->texture = NULL;
    ob->texture_repeat_x = ob->texture_repeat_y = 1.0;
    ob->texture_offset_x = ob->texture_offset_y = 0.0;
    ob->kids = NULL;
    ob->num_kids = 0;
    ob->matrix[0] = 1;
    ob->matrix[1] = 0;
    ob->matrix[2] = 0;
    ob->matrix[3] = 0;
    ob->matrix[4] = 1;
    ob->matrix[5] = 0;
    ob->matrix[6] = 0;
    ob->matrix[7] = 0;
    ob->matrix[8] = 1;
}


void init_surface(ACSurface *s)
{
    s->num_vertref = 0;
    s->flags = 0;
    s->mat = 0;
//    s->normal.x = 0.0; s->normal.z = 0.0; s->normal.z = 0.0; 
}


void osgtri_calc_normal(osg::Vec3 &v1, osg::Vec3 &v2, osg::Vec3 &v3, osg::Vec3 &n)
{
    osg::Vec3 side1=v2-v1;
    osg::Vec3 side2=v3-v2;
    n=side1^side2;
    n.normalize();

}

ACSurface *read_surface(FILE *f, ACSurface *s,osg::UShortArray *nusidx,
                        osg::Vec2Array *tcs)
{
char t[20];
    init_surface(s);

    while (!feof(f))
    {
    read_line(f);
    sscanf(buff, "%s", t);

    if (streq(t, "SURF"))
        {
        int flgs;

        if (get_tokens(buff, &tokc, tokv) != 2)
            {
            printf("SURF should be followed by one flags argument\n");
            }
        else
            {
            flgs = strtol(tokv[1], NULL, 0);
            s->flags = flgs;
            }
        }
    else
    if (streq(t, "mat"))
        {
        int mindx;

        sscanf(buff, "%s %d", t, &mindx);
        s->mat = mindx+startmatindex;
        }
    else
    if (streq(t, "refs"))
        {
        int num, n;
        int ind;
        osg::Vec2 tx;
  
          sscanf(buff, "%s %d", t, &num);        

        s->num_vertref = num;

        for (n = 0; n < num; n++)
           {
           fscanf(f, "%d %f %f\n", &ind, &tx[0], &tx[1]); line++;
            nusidx->push_back(ind);
            if (tcs) tcs->push_back(tx);
           }

        return(s);
        }
    else
        printf("ignoring %s\n", t);

    }
   return(NULL);
}




/*void ac_object_calc_vertex_normals(ACObject *ob)
{
int s, v, vr;

    / ** for each vertex in this object ** /
    for (v = 0; v < ob->num_vert; v++)
        {
        ACNormal n = {0, 0, 0};
        int found = 0;

        / ** go through each surface ** /
        for (s = 0; s < ob->num_surf; s++)
            {
            ACSurface *surf = &ob->surfaces[s];

            / ** check if this vertex is used in this surface ** /
            / ** if it is, use it to create an average normal ** /
            for (vr = 0; vr < surf->num_vertref; vr++)
                if (surf->vertref[vr] == v)
                    {
                    n.x+=surf->normal.x;
                    n.y+=surf->normal.y;
                    n.z+=surf->normal.z;
                    found++;
                    }
            }
        if (found > 0)
            {
            n.x /= found;
            n.y /= found;
            n.z /= found;
            }
        ob->vertices[v].normal = n;
        }
    

} */


int string_to_objecttype(char *s)
{
    if (streq("world", s))
        return(OBJECT_WORLD);
    if (streq("poly", s))
        return(OBJECT_NORMAL);
    if (streq("group", s))
        return(OBJECT_GROUP);
    if (streq("light", s))
        return(OBJECT_LIGHT);
    return(OBJECT_NORMAL);
}

void
protate(osg::Vec3 p, float m[9])
{ // p-> m*p, m is 3.3 matrix
    osg::Vec3 t=p;
    p[0]=m[0]*t[0]+m[1]*t[1]+m[2]*t[3];
    p[1]=m[3]*t[0]+m[4]*t[1]+m[5]*t[3];
    p[2]=m[6]*t[0]+m[7]*t[1]+m[8]*t[3];
}

osg::Group *ac_load_object(FILE *f,const ACObject *parent)
{
    // most of this logic came from Andy Colebourne (developer of the AC3D editor) so it had better be right!
    char t[20];
    osg::Group *gp=NULL;
    osg::Geode *geode=NULL;
    osg::Vec3Array *normals = NULL; // new osg::Vec3Array; // NULL;

    ACObject ob; // local storage for stuff taken from AC's loader
    osg::Vec3Array *vertpool = new osg::Vec3Array;

    if (parent) ob.loc=parent->loc; // copy loc
    while (!feof(f))
        {
    read_line(f);

    sscanf(buff, "%s", t);

        if (streq(t, "MATERIAL"))
            {
            if (get_tokens(buff, &tokc, tokv) != 22)
                {
                printf("expected 21 params after \"MATERIAL\" - line %d\n", line);
                }
            else
                {
            osg::Material *numat=new osg::Material();
            osg::Vec4 cdiff((float)atof(tokv[3]), (float)atof(tokv[4]), 
                (float)atof(tokv[5]), 1.0-(float)atof(tokv[21]));
            numat->setDiffuse(osg::Material::FRONT_AND_BACK,cdiff);
            osg::Vec4 camb((float)atof(tokv[7]),(float)atof(tokv[8]),
                (float)atof(tokv[9]),1.0-(float)atof(tokv[21]));
            numat->setAmbient(osg::Material::FRONT_AND_BACK,camb);
            osg::Vec4 cspec((float)atof(tokv[15]), (float)atof(tokv[16]),
                (float)atof(tokv[17]),1.0-(float)atof(tokv[21]));
            numat->setSpecular(osg::Material::FRONT_AND_BACK,cspec);
            osg::Vec4 cemm((float)atof(tokv[11]),(float)atof(tokv[12]),(float)atof(tokv[13]),1.0-(float)atof(tokv[21]));
            numat->setSpecular(osg::Material::FRONT_AND_BACK,cemm);
            //numat->setTransparency(osg::Material::FRONT_AND_BACK, 1.0-(float)atof(tokv[21]));

            palette[num_palette++] = numat;

              }
            }
        else
        if (streq(t, "OBJECT"))
            {
            char type[20];
            char str[20];
            osg::Vec3 loc=ob.loc;
            if (!gp) gp = new osg::Group();
            initobject(&ob); // rezero data for object
            ob.loc=loc;

            sscanf(buff, "%s %s", str, type);

            ob.type = string_to_objecttype(type);
            }
        else
        if (streq(t, "data"))
            {
            if (get_tokens(buff, &tokc, tokv) != 2)
                printf("expected 'data <number>' at line %d\n", line);
            else
                {
                char *str;
                int len;

                len = atoi(tokv[1]);
                if (len > 0)
                    {
                    str = (char *)myalloc(len+1);
                    fread(str, len, 1, f);
                    str[len] = 0;
                    fscanf(f, "\n"); line++;
                ob.data = STRING(str);
                    myfree(str);
                    }
                }
            }
        else
        if (streq(t, "name"))
            {
            int numtok = get_tokens(buff, &tokc, tokv);
            if (numtok != 2)
                {
                 printf("expected quoted name at line %d (got %d tokens)\n", line, numtok);
                }
            else
                if (gp) gp->setName(tokv[1]);
            }
        else
        if (streq(t, "texture"))
            {
            if (get_tokens(buff, &tokc, tokv) != 2)
                printf("expected quoted texture name at line %d\n", line);

            else
                {
                    osg::Image *ctx= osgDB::readImageFile(tokv[1]);
                    if (ctx) { // image coukd be read
                        ob.texture = new osg::Texture2D;// ac_load_texture(tokv[1]);
                        ob.texture->setImage(ctx);
                        if (ob.texture_repeat_x > 0.1) {
                            ob.texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
                        } else {
                            ob.texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP);
                        }
                        if (ob.texture_repeat_y > 0.1) {
                            ob.texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
                        } else {
                            ob.texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP);
                        }
                    }
                }
            }
        else
        if (streq(t, "texrep"))
            {
            if (get_tokens(buff, &tokc, tokv) != 3)
                printf("expected 'texrep <float> <float>' at line %d\n", line);
            else
                {
                ob.texture_repeat_x = atof(tokv[1]);
                ob.texture_repeat_y = atof(tokv[2]);
                }
            }
        else
        if (streq(t, "texoff"))
            {
            if (get_tokens(buff, &tokc, tokv) != 3)
                printf("expected 'texoff <float> <float>' at line %d\n", line);
            else
                {
                ob.texture_offset_x = atof(tokv[1]);
                ob.texture_offset_y = atof(tokv[2]);
                }
            }
        else
        if (streq(t, "rot"))
            {
            float r[9];
            char str2[5];
            int n;

            sscanf(buff, "%s %f %f %f %f %f %f %f %f %f", str2, 
                &r[0], &r[1], &r[2], &r[3], &r[4], &r[5], &r[6], &r[7], &r[8] );

            for (n = 0; n < 9; n++)
                ob.matrix[n] = r[n];

            }
        else
        if (streq(t, "loc"))
            {
            char str[5];
            osg::Vec3 loc;
            sscanf(buff, "%s %f %f %f", str, &loc[0], &loc[1], &loc[2]);
            ob.loc+=loc;
            }
        else
        if (streq(t, "url"))
            {
	    int ret;
            if ((ret = get_tokens(buff, &tokc, tokv)) != 2)
                printf("expected one arg to url at line %d (got %d)\n", line, ret); 
            else
                ob.url = STRING(tokv[1]);
            }
        else
        if (streq(t, "numvert"))
            {
            int num, n;
            char str[10];
            if (ob.type == OBJECT_GROUP || ob.type == OBJECT_WORLD) {
            } else if (ob.type == OBJECT_NORMAL) {
                if (geode) {
                    osgUtil::Tesselator tesselator;
                    for(unsigned int i=0;i<geode->getNumDrawables();++i)
                    {
                        osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
                        if (geom) tesselator.retesselatePolygons(*geom);
                    }
                }
                geode = new osg::Geode();
                gp->addChild(geode);
                geode->setName(gp->getName());
                normals = new osg::Vec3Array;
            }

                sscanf(buff, "%s %d", str, &num);

                if (num > 0)
                    {
                    ob.num_vert = num;

                    for (n = 0; n < num; n++)
                        {
                        osg::Vec3 p;
                        fscanf(f, "%f %f %f\n", &p[0], &p[1], &p[2]); line++;
                        protate(p, ob.matrix);
                        vertpool->push_back(p+ob.loc);
                        }

                    }
            }
        else
            if (streq(t, "numsurf"))
            {
                int num, n;
                char str[10];
                // this is not obvious (what is?).  Each set of surfaces can have different material on each surface.
                // so I set up a set of 'bins' for 
                //    the primitives (geometry list, geomlist)
                //    the coords array of each geometry (Vec3Array list, vertslist)
                //    the tx coords array for each geometry (Vec2Array list, texslist)
                // then I add a new geometry to the current Geode for each new material as it is found.

                std::vector<int> ia; // list of materials required- generate one geode per material
                typedef std::vector<osg::Geometry *> geomlist;
                geomlist glist;
                typedef std::vector<osg::Vec3Array *> vertslist;
                vertslist vlists; // list of vertices for each glist element
                typedef std::vector<osg::Vec2Array *> texslist;
                texslist txlists; // list of texture coords for each glist element
                
                sscanf(buff, "%s %d", str, &num);
                if (num > 0)
                {
                    int needSmooth=0; // flat shaded
                    ob.num_surf = num;
                    
                    for (n = 0; n < num; n++)
                    {
                        osg::Geometry *geom=NULL; // the surface will be addded to this geometry
                        osg::Vec3Array *vgeom=NULL; // vertices corresponding to geom taken from vertexpool
                        osg::Vec2Array *tgeom=NULL; // texture coords corresponding to geom taken from vertexpool
                        ACSurface asurf;
                        osg::UShortArray *nusidx = new osg::UShortArray; // indices into the vertices
                        osg::Vec2Array *tcs=new osg::Vec2Array; // texture coordinates for this object
                        ACSurface *news = read_surface(f, &asurf, nusidx, tcs);
                        if (news == NULL)
                        {
                            printf("error whilst reading surface at line: %d\n", line);
                            return(NULL);
                        } else {
                            int i=0;
                            for (std::vector<int>::iterator itr= ia.begin(); itr<ia.end(); itr++, i++) {
                                if ((*itr)==asurf.mat) {
                                    geom=glist[i];
                                    vgeom=vlists[i];
                                    tgeom=txlists[i]; // what is current texture array
                                }
                            }
                            if (!geom) { // then we need a new geometry
                                osg::Vec3Array *verts = new osg::Vec3Array;

                                osg::Vec2Array *tcrds=new osg::Vec2Array; // texture coordinates for this object
                                vgeom=verts;
                                tgeom=tcrds; // what is current texture array
                                vlists.push_back(verts);
                                txlists.push_back(tcrds);
                                geom=new osg::Geometry();
                                geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
                                geom->setNormalArray(normals);
                                geom->setVertexArray(verts);
                                if (ob.texture) {
                                    geom->setTexCoordArray(0,tgeom); // share same set of TexCoords
                                }
                                osg::Material*mat=ac_palette_get_material(asurf.mat);
                                osg::StateSet *dstate = new osg::StateSet;
                                dstate->setMode( GL_LIGHTING, osg::StateAttribute::ON );
                                dstate->setAttribute(mat);
                                const osg::Vec4 cdiff =mat->getDiffuse(osg::Material::FRONT_AND_BACK);
                                if (cdiff[3]<0.99) {
                                    dstate->setMode(GL_BLEND,osg::StateAttribute::ON);
                                    dstate->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
                                } else {
                                    dstate->setMode(GL_BLEND,osg::StateAttribute::OFF);
                                }
                                if (ob.texture) dstate->setTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF);
                                if (ob.texture) {
                                    osg::TexEnv* texenv = new osg::TexEnv;
                                    texenv->setMode(osg::TexEnv::MODULATE);
                                    dstate->setTextureAttribute(0, texenv );
                                    dstate->setTextureAttributeAndModes(0,ob.texture,osg::StateAttribute::ON);
                                }
                                if (asurf.flags & SURFACE_TWOSIDED) dstate->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
                                else dstate->setMode( GL_CULL_FACE, osg::StateAttribute::ON );

                                geom->setStateSet( dstate );
                                glist.push_back(geom);
                                geode->addDrawable(geom);
                                ia.push_back(asurf.mat);
                            }
                            
                            osg::Vec3Array* normals = geom->getNormalArray();
                            /** calc surface normal **/
                            if (asurf.num_vertref >= 3) {
                                osg::Vec3 norm;
                                unsigned short i1=(*nusidx)[0];
                                unsigned short i2=(*nusidx)[1];
                                unsigned short i3=(*nusidx)[2];
                                osgtri_calc_normal((*vertpool)[i1], 
                                    (*vertpool)[i2], 
                                    (*vertpool)[i3], norm);
                                normals->push_back(norm);
                            }
                            int nstart=(*vgeom).size();
                            for (i=0; i<asurf.num_vertref; i++) {
                                int i1=(*nusidx)[i];
                                (*vgeom).push_back((*vertpool)[i1]);
                                (*tgeom).push_back((*tcs)[i]);
                            }
                            GLenum poltype=osg::PrimitiveSet::POLYGON;
                            if (asurf.flags & SURFACE_TYPE_CLOSEDLINE) poltype=osg::PrimitiveSet::LINE_LOOP;
                            if (asurf.flags & SURFACE_TYPE_LINE) poltype=osg::PrimitiveSet::LINE_STRIP;
                            geom->addPrimitiveSet(new osg::DrawArrays(poltype,nstart,asurf.num_vertref));
                            if (asurf.flags & 0x10) needSmooth++;
                        }
                    }
                    for (geomlist::iterator itr= glist.begin(); itr<glist.end(); itr++) {
                        osgUtil::Tesselator tesselator;
                        if (*itr) tesselator.retesselatePolygons(**itr);
                        if (needSmooth) {
                            osgUtil::SmoothingVisitor smoother;
                            smoother.smooth(**itr);
                        }
                    }
                }
            }
        else
        if (streq(t, "kids")) /** 'kids' is the last token in an object **/
            {
            int num, n;

            sscanf(buff, "%s %d", t, &num);
            
                if (num != 0)
                    {
                    // ob.kids = (ACObject **)myalloc(num * sizeof(ACObject *) );
                    ob.num_kids = num;

                    for (n = 0; n < num; n++)
                        {
                        osg::Group *k = ac_load_object(f,&ob); //, ob);

                        if (k == NULL)
                            {
                            printf("error reading expected child object %d of %d at line: %d\n", n+1, num, line);
                            return(gp);
                            }
                        else
                            //ob.kids[n] = k;
                            gp->addChild(k);
                        }

                    }
                if (geode) {
                    osgUtil::Tesselator tesselator;
                    for(unsigned int i=0;i<geode->getNumDrawables();++i)
                    {
                        osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
                        if (geom) tesselator.retesselatePolygons(*geom);
                    }
                }
            return(gp);
            }

    }
    if (geode) {
        osgUtil::Tesselator tesselator;
        for(unsigned int i=0;i<geode->getNumDrawables();++i)
        {
            osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
            if (geom) tesselator.retesselatePolygons(*geom);
        }
    }
    return(gp);

}


/*void ac_calc_vertex_normals(ACObject *ob)
{
int n;

    ac_object_calc_vertex_normals(ob);
    if (ob->num_kids)
        for (n = 0; n < ob->num_kids; n++)
            ac_calc_vertex_normals(ob->kids[n]);
}*/


osg::Group *ac_load_ac3d(const char *fname)
{
FILE *f = fopen(fname, "r");

osg::Group *ret = NULL;

    if (f == NULL)
        {
        printf("can't open %s\n", fname);
        return(NULL);
        }

    read_line(f);

    if (strncmp(buff, "AC3D", 4))
        {
        printf("ac_load_ac '%s' is not a valid AC3D file.", fname);
        fclose(f);
        return(0);
        }


    startmatindex = num_palette;


    ret = ac_load_object(f,NULL); //, NULL);

    
    fclose(f);

    // ac_calc_vertex_normals(ret);
    // here I need to calculate nromals for this object

    return(ret);
}







void ac_dump(ACObject *ob)
{ // not yet finished option ot output AC3D file from OSG scene.
int n;

    printf("OBJECT name %s\nloc %f %f %f\nnum_vert %d\nnum_surf %d\n",
        ob->name, ob->loc[0], ob->loc[1], ob->loc[2], ob->num_vert, ob->num_surf);


//    for (n=0; n < ob->num_vert; n++)
//        printf("\tv %f %f %f\n", ob->vertices[n].x, ob->vertices[n].y, ob->vertices[n].z);

    for (n=0; n < ob->num_surf; n++)
        {
////        ACSurface *s = &ob->surfaces[n];
//        printf("surface %d, %d refs, mat %d\n", n, s->num_vertref, s->mat);
        }

/* this structure not used in OSG
    if (ob->num_kids)
        for (n = 0; n < ob->num_kids; n++)
            ac_dump(ob->kids[n]); 
 */            
}



// now register with osg::Registry to instantiate the above
// reader/writer.
osgDB::RegisterReaderWriterProxy<ReaderWriterAC> g_readerWriter_AC_Proxy;




















