#pragma once

// future API - just run through once to convert from OSG to SG
// then we can use these triangle lists for random 
// trees/lights/buildings/objects
struct SGTexturedTriangle
{
public:
    std::vector<SGVec3f> vertices;
    std::vector<SGVec2f> texcoords;
};

struct SGBorderContour
{
public:
    SGVec3d start;
    SGVec3d end;
};

class SGTriangleInfo
{
public:
    SGTriangleInfo( const SGVec3d& center ) {
        gbs_center = center;
        mt_init(&seed, 123);
    }

    // API used to build the Info by the visitor
    void addGeometry( osg::Geometry* g ) { 
        geometries.push_back(g); 
    }
    
    void setMaterial( SGMaterial* m ) { 
        mat = m; 
    }

    SGMaterial* getMaterial( void ) const {
        return mat; 
    }
    
    // API used to get a specific texture or effect from a material.  Materials can have
    // multiple textures - use the floor of the x coordinate of the first vertes to select it.
    // This will be constant, and give the same result each time to select one effect/texture per drawable.
    int getTextureIndex( void ) const {
        int texInfo = 0;
        const osg::Vec3Array* vertices =  dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
        if ( vertices ) {
            const osg::Vec3 *v0 = &vertices->operator[](0);
            texInfo = floor(v0->x()); 
        }
        return texInfo;
    }
    
    // new API - TODO
    void getTriangles( std::vector<SGTexturedTriangle>& tris )
    {
        const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
        const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
        
        int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
        if ( numPrimitiveSets > 0 ) {
            const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
            unsigned int numIndices = ps->getNumIndices();
            
            for ( unsigned int i=2; i<numIndices; i+= 3 ) {
                SGTexturedTriangle tri;
                
                tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-2))) );
                tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-1))) );
                tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-0))) );
                
                tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-2))) );
                tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-1))) );
                tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-0))) );
            }
        }
    }
    
    void getBorderContours( std::vector<SGBorderContour>& border )
    {
        // each structure contains a list of target indexes and a count
        int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
        if ( numPrimitiveSets > 0 ) {
            const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());

            const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
            unsigned int numTriangles = ps->getNumIndices()/3;

            // use a map for fast lookup map the segment as a 64 bit int
            std::map<uint64_t, int> segCounter;
            uint32_t idx1, idx2;
            uint64_t key;
            
            for ( unsigned int i=0; i<numTriangles; i+= 3 ) {
                // first seg
                if ( ps->index(i+0) < ps->index(i+1) ) {
                    idx1 = ps->index(i+0);
                    idx2 = ps->index(i+1);
                } else {
                    idx1 = ps->index(i+1);
                    idx2 = ps->index(i+0);                    
                }
                
                key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
                SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
                segCounter[key]++;
                SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
                
                // second seg
                if ( ps->index(i+1) < ps->index(i+2) ) {
                    idx1 = ps->index(i+1);
                    idx2 = ps->index(i+2);
                } else {
                    idx1 = ps->index(i+2);
                    idx2 = ps->index(i+1);             
                }
                
                key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
                SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
                segCounter[key]++;
                SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
                
                // third seg
                if ( ps->index(i+2) < ps->index(i+0) ) {
                    idx1 = ps->index(i+2);
                    idx2 = ps->index(i+0);
                } else {
                    idx1 = ps->index(i+0);
                    idx2 = ps->index(i+2);                    
                }
                
                key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
                SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
                segCounter[key]++;
                SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
            }
            
            // return all segments with count = 1 ( border )
            std::map<uint64_t, int>::iterator segIt = segCounter.begin();            
            while ( segIt != segCounter.end() ) {
                if ( segIt->second == 1 ) {
                    SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << segIt->first << std::dec << " count is " << segIt->second );
                    
                    unsigned int iStart = segIt->first >> 32;
                    unsigned int iEnd   = segIt->first & 0x00000000FFFFFFFF;
                    
                    SGBorderContour bc;
                    
                    bc.start = toVec3d(toSG(vertices->operator[](iStart)));
                    bc.end   = toVec3d(toSG(vertices->operator[](iEnd)));
                    border.push_back( bc );
                }
                segIt++;
            }
            
#if 0
            // debug out - requires GDAL
            //
            //
            //
            SGGeod  geodPos = SGGeod::fromCart(gbs_center);
            SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);

            for ( unsigned int i=0; i<border.size(); i++ ){
                // de-rotate and translate : todo - create a paralell vertex list so we just do this 
                // once per vertex, not for every triangle's use of the vertex
                SGVec3d sgVStart = hlOr.backTransform( border[i].start) + gbs_center;
                SGVec3d sgVEnd   = hlOr.backTransform( border[i].end)   + gbs_center;
                    
                // convert from cartesian to Geodetic, and save as a list of Geods for output
                SGGeod gStart = SGGeod::fromCart(sgVStart);
                SGGeod gEnd   = SGGeod::fromCart(sgVEnd);
                
                SGShapefile::FromSegment( gStart, gEnd, true, "./borders", mat->get_names()[0], "border" );
            }
#endif            
        }
    }
        
    // Random buildings API - get num triangles, then get a triangle at index
    unsigned int getNumTriangles( void ) const {
        unsigned int num_triangles = 0;

        if ( !geometries.empty() ) {
            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
            if ( numPrimitiveSets > 0 ) {
                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
                unsigned int numIndices = ps->getNumIndices();
                num_triangles = numIndices/3;
            }
        }
        
        return num_triangles;
    }
    
    void getTriangle(unsigned int i, std::vector<SGVec3f>& triVerts, std::vector<SGVec2f>& triTCs) const {
        const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
        const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));

        if ( !geometries.empty() ) {
            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
            if ( numPrimitiveSets > 0 ) {
                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
                int idxStart = i*3;
                
                triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+0))) );
                triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+1))) );
                triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+2))) );
                
                triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+0))) );
                triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+1))) );
                triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+2))) );
            }
        }
    }
    
    // random lights and trees - just get a list of points on where to add the light / tree
    // TODO move this out - and handle in the random light / tree code
    // just use generic triangle API.
    void addRandomSurfacePoints(float coverage, float offset,
                                osg::Texture2D* object_mask,
                                std::vector<SGVec3f>& points)
    {
        if ( !geometries.empty() ) {
            const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
            const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
            
            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
            if ( numPrimitiveSets > 0 ) {
                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
                unsigned int numIndices = ps->getNumIndices();
                
                for ( unsigned int i=2; i<numIndices; i+= 3 ) {                    
                    SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
                    SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
                    SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));
                    
                    SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
                    SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
                    SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));
                    
                    SGVec3f normal = cross(v1 - v0, v2 - v0);
            
                    // Compute the area
                    float area = 0.5f*length(normal);
                    if (area <= SGLimitsf::min())
                        continue;
            
                    // For partial units of area, use a zombie door method to
                    // create the proper random chance of a light being created
                    // for this triangle
                    float unit = area + mt_rand(&seed)*coverage;
            
                    SGVec3f offsetVector = offset*normalize(normal);
                    // generate a light point for each unit of area
            
                    while ( coverage < unit ) {
                        float a = mt_rand(&seed);
                        float b = mt_rand(&seed);
                
                        if ( a + b > 1 ) {
                            a = 1 - a;
                            b = 1 - b;
                        }
                        float c = 1 - a - b;
                        SGVec3f randomPoint = offsetVector + a*v0 + b*v1 + c*v2;
                
                        if (object_mask != NULL) {
                            SGVec2f texCoord = a*t0 + b*t1 + c*t2;
                    
                            // Check this random point against the object mask
                            // blue channel.
                            osg::Image* img = object_mask->getImage();            
                            const uint32_t x = static_cast<uint32_t>(img->s() * texCoord.x()) % img->s();
                            const uint32_t y = static_cast<uint32_t>(img->t() * texCoord.y()) % img->t();
                    
                            if (mt_rand(&seed) < img->getColor(x, y).b()) {
                                points.push_back(randomPoint);        
                            }                    
                        } else {      
                            // No object mask, so simply place the object  
                            points.push_back(randomPoint);        
                        }
                        unit -= coverage;        
                    }
                }
            }
        }
    }

    void addRandomTreePoints(float wood_coverage, 
                             osg::Texture2D* object_mask,
                             float vegetation_density,
                             float cos_max_density_angle,
                             float cos_zero_density_angle,
                             bool is_plantation,
                             std::vector<SGVec3f>& points,
			     std::vector<SGVec3f>& normals)
    {
        using std::max;
        using std::min;

        if ( !geometries.empty() ) {
            const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
            const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));

            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
            if ( numPrimitiveSets > 0 ) {
                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
                unsigned int numIndices = ps->getNumIndices();

                for ( unsigned int i=2; i<numIndices; i+= 3 ) {                    
                    SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
                    SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
                    SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));

                    SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
                    SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
                    SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));

                    SGVec3f normal = cross(v1 - v0, v2 - v0);

                    // Ensure the slope isn't too steep by checking the
                    // cos of the angle between the slope normal and the
                    // vertical (conveniently the z-component of the normalized
                    // normal) and values passed in.                   
                    float alpha = normalize(normal).z();
                    float slope_density = 1.0;

                    if (alpha < cos_zero_density_angle) 
                        continue; // Too steep for any vegetation      

                    if (alpha < cos_max_density_angle) {
                        slope_density = 
                        (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle);
                    }

                    // Compute the area
                    float area = 0.5f*length(normal);
                    if (area <= SGLimitsf::min())
                        continue;

                    if (is_plantation)  {  // regularly-spaced vegetation
                        // separate vegetation in integral 1m units
                        int separation = (int) ceil(sqrt(wood_coverage));
                        float max_x = ceil(max(max(v1.x(),v2.x()),v0.x()));
                        float min_x = floor(min(min(v1.x(),v2.x()),v0.x()));
                        float max_y = ceil(max(max(v1.y(),v2.y()),v0.y()));
                        float min_y = floor(min(min(v1.y(),v2.y()),v0.y()));

                        /* equation of the plane ax+by+cz+d=0, need d */

                        float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z());
                        /* Now loop over a grid, skipping points not in the triangle */
                        int x_steps = (int) (max_x - min_x)/separation;
                        int y_steps = (int) (max_y - min_y)/separation;
                        SGVec2f v02d = SGVec2f(v0.x(),v0.y());
                        SGVec2f v12d = SGVec2f(v1.x(),v1.y());
                        SGVec2f v22d = SGVec2f(v2.x(),v2.y());

                        for (int jx = 0; jx < x_steps; jx++) {
                            float ptx = min_x + jx * separation;

                            for (int jy = 0; jy < y_steps; jy++) {
                                float pty = min_y + jy * separation;
                                SGVec2f newpt = SGVec2f(ptx,pty);
                                if (!point_in_triangle(newpt,v02d,v12d,v22d))
                                    continue;

                                // z = (-ax-by-d)/c; c is not zero as
                                // that would be alpha of 1.0

                                float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z();
                                SGVec3f randomPoint = SGVec3f(ptx,pty,ptz);

                                if (object_mask != NULL) {
                                    // Check this point against the object mask
                                    // green (for trees) channel.
                                    osg::Image* img = object_mask->getImage();
                                    unsigned int x = (int) (img->s() * newpt.x()) % img->s();
                                    unsigned int y = (int) (img->t() * newpt.y()) % img->t();

                                    if (mt_rand(&seed) < img->getColor(x, y).g()) {
                                        // The red channel contains the rotation for this object
                                        points.push_back(randomPoint);
                                        normals.push_back(normalize(normal));
                                    }
                                } else {
                                    points.push_back(randomPoint);
                                    normals.push_back(normalize(normal));
                                }
                            }
                        }
                    }  else  {
                        // Determine the number of trees, taking into account vegetation
                        // density (which is linear) and the slope density factor.
                        // Use a zombie door method to create the proper random chance 
                        // of a tree being created for partial values.
                        int woodcount = (int) (vegetation_density * vegetation_density * 
                                               slope_density *
                                               area / wood_coverage + mt_rand(&seed));
                        for (int j = 0; j < woodcount; j++) {
                            // Use barycentric coordinates
                            float a = mt_rand(&seed);
                            float b = mt_rand(&seed);

                            if ( a + b > 1.0f ) {
                                a = 1.0f - a;
                                b = 1.0f - b;
                            }

                            float c = 1.0f - a - b;

                            SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
                            if (object_mask != NULL) {
                                SGVec2f texCoord = a*t0 + b*t1 + c*t2;

                                // Check this random point against the object mask
                                // green (for trees) channel.
                                osg::Image* img = object_mask->getImage();       
                                const uint32_t x = static_cast<uint32_t>(img->s() * texCoord.x()) % img->s();
                                const uint32_t y = static_cast<uint32_t>(img->t() * texCoord.y()) % img->t();

                                if (mt_rand(&seed) < img->getColor(x, y).g()) {
                                    // The red channel contains the rotation for this object
                                    points.push_back(randomPoint);
                                    normals.push_back(normalize(normal));
                                }
                            } else {
                                points.push_back(randomPoint);
                                normals.push_back(normalize(normal));
                            }
                        }
                    }
                }
            }
        }
    }

#if 0    
    // debug : this will save the tile as a shapefile that can be viewed in QGIS.
    // NOTE: this is really slow....
    // remember - we need to de-rotate the tile, then translate back to gbs_center.
    void dumpBorder() {
        //dump the first triangle only of the first geometry, for now...
        SG_LOG(SG_TERRAIN, SG_ALERT, "effect geode has " << geometries.size() << " geometries" );
        
        const osg::Vec3Array* vertices =  dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
        if ( vertices ) {
            SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << vertices->getNumElements() << " vertices" );
        }
        
        if ( !geometries.empty() ) {
            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
            SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << numPrimitiveSets << " primitive sets" );
            
            if ( numPrimitiveSets > 0 ) {
                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
                unsigned int numIndices = ps->getNumIndices();

                // create the same quat we used to rotate here 
                // - use backTransform to go back to original node location
                SGGeod  geodPos = SGGeod::fromCart(gbs_center);
                SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
                
                SG_LOG(SG_TERRAIN, SG_ALERT, "  primitive set has has " << numIndices << " indices" );                
                for ( unsigned int i=2; i<numIndices; i+= 3 ) {
                    if ( numIndices >= 3 ) {                    
                        unsigned int v0i = ps->index(i-2);
                        unsigned int v1i = ps->index(i-1);
                        unsigned int v2i = ps->index(i-0);
                    
                        const osg::Vec3 *v0 = &vertices->operator[](v0i);
                        const osg::Vec3 *v1 = &vertices->operator[](v1i);
                        const osg::Vec3 *v2 = &vertices->operator[](v2i);
                   
                        // de-rotate and translate : todo - create a paralell vertex list so we just do this 
                        // once per vertex, not for every triangle's use of the vertex
                        SGVec3d vec0 = hlOr.backTransform( toVec3d(toSG(*v0))) + gbs_center;
                        SGVec3d vec1 = hlOr.backTransform( toVec3d(toSG(*v1))) + gbs_center;
                        SGVec3d vec2 = hlOr.backTransform( toVec3d(toSG(*v2))) + gbs_center;
                    
                        // convert from cartesian to Geodetic, and save as a list of Geods for output
                        std::vector<SGGeod> triangle;
                        triangle.push_back( SGGeod::fromCart(vec0) );
                        triangle.push_back( SGGeod::fromCart(vec1) );
                        triangle.push_back( SGGeod::fromCart(vec2) );
                    
                        SGShapefile::FromGeodList( triangle, true, "./triangles", mat->get_names()[0], "tri" );
                    }
                }
            }            
            
        }
    }
#endif    
    
private:
    mt seed;
    SGMaterial* mat;
    SGVec3d gbs_center;
    std::vector<osg::Geometry*> geometries;
    std::vector<int> polygon_border;    // TODO
};

// This visitor will generate an SGTriangleInfo.
// currently, it looks like it could save multiple lists, which could be the case
// if multiple osg::geods are found with osg::Geometry.
// But right now, we store a single PrimitiveSet under a single EffectGeod.  
// so the traversal should only find a single EffectGeod - building a single SGTriangleInfo
class GetNodeTriangles : public osg::NodeVisitor
{
public:
    GetNodeTriangles(const SGVec3d& c, std::vector<SGTriangleInfo>* nt) : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), center(c), nodeTris(nt) {}
    
    // This method gets called for every node in the scene
    //   graph. Check each node to see if it has user
    //   out target. If so, save the node's address.
    virtual void apply( osg::Node& node )
    {
        EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
        if ( eg ) {
            // get the material from the user info
            SGTriangleInfo triInfo( center );
            triInfo.setMaterial( eg->getMaterial() );

            // let's find the drawables for this node
            int numDrawables = eg->getNumDrawables();
            for ( int i=0; i<numDrawables; i++ ) {
                triInfo.addGeometry( eg->getDrawable(i)->asGeometry() );
            }
            
            nodeTris->push_back( triInfo );
        }
        
        // Keep traversing the rest of the scene graph.
        traverse( node );
    }
    
protected:
    SGVec3d                         center;
    std::vector<SGTriangleInfo>*    nodeTris;
};
