/**
 @file AMUtil.cpp

 Dojo Game Engine

 Copyright 2005, Morgan McGuire
 All rights reserved.

 Helper methods for ArticulatedModels.
 */

#include "dojo/AMUtil.h"

ArticulatedModelRef createSpaceShip(const std::string& path) {
    CoordinateFrame xform;

    xform.rotation[0][0] = xform.rotation[1][1] = xform.rotation[2][2] = 0.008f;

    const Matrix3 rot180 = Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(180));
    xform.rotation = xform.rotation * rot180;
    xform.translation = Vector3(0, 0.1f, -0.2f);
    ArticulatedModelRef model = ArticulatedModel::fromFile(path + "SpaceFighter01.3ds", xform);

    // Override the textures in the file with more interesting ones

    TextureRef diffuse = Texture::fromFile(path + "diffuse.jpg");
    {
        SuperShader::Material& material = model->partArray[0].triListArray[0].material;
        material.emit = Texture::fromFile(path + "emit.jpg");
        material.diffuse.map = diffuse;
        material.diffuse.constant = Color3::white() * 0.8f;
        material.transmit = Color3::black();
        material.specular = Texture::fromFile(path + "specular.jpg");
        material.reflect = Color3::black();
        material.specularExponent = Color3::white() * 40;
    }

    {
        SuperShader::Material& material = model->partArray[0].triListArray[1].material;
        material.emit = Color3::black();
        material.diffuse.map = diffuse;
        material.diffuse.constant = Color3::white() * 0.5f;
        material.transmit = Color3::black();
        material.specular = Color3::green() * .5f;
        material.reflect = Color3::white() * 0.4f;
        material.specularExponent = Color3::white() * 40;
    }

    model->updateAll();

    return model;
}

ArticulatedModelRef createIFSModel(const std::string& filename, Color3 color, const CoordinateFrame& cframe) {
    ArticulatedModelRef model = ArticulatedModel::fromFile(filename, cframe);

    SuperShader::Material& material = model->partArray[0].triListArray[0].material;
    material.diffuse = color;
    material.transmit = Color3::black();
    material.reflect = Color3::black();
    material.specular = Color3::white() * .05f;
    material.specularExponent = Color3::white() * 10;
    model->updateAll();

    return model;
}

/** Convert a 3DS file to an IFS file */
static void convert(const std::string& name3ds, const std::string& nameIFS, const CoordinateFrame& s) {
    ArticulatedModelRef model = ArticulatedModel::fromFile(std::string("stickfigure/") + name3ds + ".3ds", s);

    ArticulatedModel::Part& part = model->partArray[0];
     IFSModel::save(std::string("stickfigure/") + nameIFS + ".ifs", nameIFS,
        part.indexArray, part.geometry.vertexArray, part.texCoordArray);

}


ArticulatedModelRef createStickFigure(SuperShader::Material& material) {
  
    ArticulatedModelRef model = ArticulatedModel::createEmpty();
    model->name = "Stick Figure";

    enum {PELVIS = 0, CHEST = 1, HEAD = 2, 
        LEFT_HUMERUS = 3, LEFT_RADIUS = 4, LEFT_HAND = 5, LEFT_FINGERS = 6, LEFT_THUMB = 7,
        LEFT_FEMUR = 8, LEFT_TIBIA = 9, LEFT_FOOT = 10,
        RIGHT_HUMERUS = 11, RIGHT_RADIUS = 12, RIGHT_HAND = 13, RIGHT_FINGERS = 14, RIGHT_THUMB = 15,
        RIGHT_FEMUR = 16, RIGHT_TIBIA = 17, RIGHT_FOOT = 18};

// Code to convert from Sam's original model
//    // Flip upside down and scale by 1/100
//    const CoordinateFrame s(Matrix3::fromAxisAngle(Vector3::unitX(), toRadians( 0)) * 0.01, Vector3::zero());
//    convert("pelvis", "pelvis", s);
//    convert("chest", "chest", s + Vector3(0, 0.65, 0));
//    convert("head", "head", s + Vector3(0, 1.1, 0));
// 
//    convert("left arm", "lhumerus", CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(90)) * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(180)), Vector3(-.94, 0, -0.5)));
//    convert("left forearm", "lradius", CoordinateFrame(0.01 *Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(-90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(-90)), Vector3(-.94, 0, -1.05)));
//    convert("left upper fingers", "lhand", CoordinateFrame(0.01 *Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(-90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(-90)), Vector3(-.94, 0, -1.5)));
//    convert("left lower fingers", "lfingers", CoordinateFrame(0.01 *Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(-90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(-90)), Vector3(-1.05, 0, -1.7)));
//    convert("left thumb", "lthumb",  CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(180)), Vector3(0, -.95, -1.6)));
//    convert("left thigh", "lfemur", s + Vector3(.2, -.14,0));
//    convert("left shin", "ltibia", s + Vector3(.2, .55,0));
//    convert("left foot", "lfoot", CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(180)), Vector3(.25, 0, 1)));
//
//    CoordinateFrame X = CoordinateFrame(Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(180))) * CoordinateFrame(Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(180)));
//    convert("right arm", "rhumerus", X * CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(90)) * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(180)), Vector3(-.94, 0, -0.5)));
//    convert("right forearm", "rradius", X * CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(-90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(-90)), Vector3(-.94, 0, -1.05)));
//    convert("right upper fingers", "rhand", X * CoordinateFrame(0.01 *Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(-90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(-90)), Vector3(-.94, 0, -1.5)));
//    convert("right lower fingers", "rfingers", X *CoordinateFrame(0.01 *Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(-90)) * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(-90)), Vector3(-1.05, 0, -1.7)));
//    convert("right thumb", "rthumb",  CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(180)), Vector3(0, -.95, -1.595)));
//
//    convert("right thigh", "rfemur", s + Vector3(-.2, -.14,0));
//    convert("right shin", "rtibia", s + Vector3(-.2, .55,0));
//    convert("right foot", "rfoot", CoordinateFrame(0.01 * Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(180)), Vector3(-.25, 0, 1)));


    {
        // Pelvis: 0
        ArticulatedModelRef pelvis = ArticulatedModel::fromFile("stickfigure/pelvis.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = pelvis->partArray.last();
        part.name = "Root";
        part.cframe = CoordinateFrame();
        // Make a reference to the children we're going to add
        part.subPartArray.append(CHEST, LEFT_FEMUR, RIGHT_FEMUR);
        part.triListArray[0].material = material;
    }

    {
        // Chest: 1
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/chest.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = PELVIS;
        part.name = "Chest";
        part.cframe = CoordinateFrame(Vector3(0, 0.65, 0) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(HEAD, LEFT_HUMERUS, RIGHT_HUMERUS);
    }

    {
        // Head: 2
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/head.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = CHEST;
        part.name = "Head";
        part.cframe = CoordinateFrame(Vector3(0, 0.45, 0) / 2);
        part.triListArray[0].material = material;
    }

    {
        // Left Humerus: 3
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/lhumerus.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = CHEST;
        part.name = "Left Humerus";
        part.cframe = CoordinateFrame(Vector3(-0.5, 0.3, 0.0) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(LEFT_RADIUS);
    }
    
    {
        // Left Radius: 4
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/lradius.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = LEFT_HUMERUS;
        part.name = "Left Radius";
        part.cframe = CoordinateFrame(Vector3(-0.55f, 0.0f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(LEFT_HAND);
    }

    {
        // Left Hand: 5
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/lhand.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = LEFT_RADIUS;
        part.name = "Left Hand";
        part.cframe = CoordinateFrame(Vector3(-0.45f, 0.0f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(LEFT_FINGERS, LEFT_THUMB);
    }

    {
        // Left Fingers: 6
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/lfingers.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = LEFT_HAND;
        part.name = "Left Fingers";
        part.cframe = CoordinateFrame(Vector3(-0.197f, 0.109f, 0.0f)/ 2);
        part.triListArray[0].material = material;
    }
    {
        // Left Thumb: 7
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/lthumb.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = LEFT_HAND;
        part.name = "Left Thumb";
        part.cframe = CoordinateFrame(Vector3(-.1f, 0.01f, 0.0f) / 2);
        part.triListArray[0].material = material;
    }

    {
        // Left Femur: 8
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/lfemur.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = PELVIS;
        part.name = "Left Femur";
        part.cframe = CoordinateFrame(Vector3(-0.2f, 0.14f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(LEFT_TIBIA);
    }

    {
        // Left Tibia: 9
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/ltibia.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = LEFT_FEMUR;
        part.name = "Left Tibia";
        part.cframe = CoordinateFrame(Vector3(0.0f, -0.35f, 0.0f));
        part.triListArray[0].material = material;
        part.subPartArray.append(LEFT_FOOT);
    }

    {
        // Left Foot: 10
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/lfoot.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = LEFT_TIBIA;
        part.name = "Left Foot";
        part.cframe = CoordinateFrame(Vector3(-0.1f, -0.45f, 0.0f) / 2);
        part.triListArray[0].material = material;
    }

    {
        // Right Humerus: 11
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/rhumerus.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = CHEST;
        part.name = "Right Humerus";
        part.cframe = CoordinateFrame(Vector3(0.5f, 0.3f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(RIGHT_RADIUS);
    }

    {
        // Right Radius: 12
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/rradius.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = RIGHT_HUMERUS;
        part.name = "Right Radius";
        part.cframe = CoordinateFrame(Vector3(0.55f, 0.0f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(RIGHT_HAND);
    }

    {
        // Right Hand: 13
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/rhand.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = RIGHT_RADIUS;
        part.name = "Right Hand";
        part.cframe = CoordinateFrame(Vector3(0.45f, 0.0f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(RIGHT_FINGERS, RIGHT_THUMB);
    }
    
    {
        // Right Fingers: 14
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/rfingers.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = RIGHT_HAND;
        part.name = "Right Fingers";
        part.cframe = CoordinateFrame(Vector3(0.197f, 0.109f, 0.0f)/ 2);
        part.triListArray[0].material = material;
    }
    {
        // Right Thumb: 15
        ArticulatedModelRef chest = ArticulatedModel::fromFile("stickfigure/rthumb.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = chest->partArray.last();
        part.parent = RIGHT_HAND;
        part.name = "Right Thumb";
        part.cframe = CoordinateFrame(Vector3(.1f, 0.01f, 0.0f) / 2);
        part.triListArray[0].material = material;
    }
    

    {
        // Right Femur: 16
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/rfemur.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = PELVIS;
        part.name = "Right Femur";
        part.cframe = CoordinateFrame(Vector3(0.2f, 0.14f, 0.0f) / 2);
        part.triListArray[0].material = material;
        part.subPartArray.append(RIGHT_TIBIA);
    }

    {
        // Right Tibia: 17
        ArticulatedModelRef body = ArticulatedModel::fromFile("stickfigure/rtibia.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = body->partArray.last();
        part.parent = RIGHT_FEMUR;
        part.name = "Right Tibia";
        part.cframe = CoordinateFrame(Vector3(0.0f, -0.35f, 0.0f));
        part.triListArray[0].material = material;
        part.subPartArray.append(RIGHT_FOOT);
    }

   {
        // Right Foot: 18
        ArticulatedModelRef bone = ArticulatedModel::fromFile("stickfigure/rfoot.ifs", 0.5);
        ArticulatedModel::Part& part = model->partArray.next();
        part = bone->partArray.last();
        part.parent = RIGHT_TIBIA;
        part.name = "Right Foot";
        part.cframe = CoordinateFrame(Vector3(0.1f, -0.45f, 0.0f) / 2);
        part.triListArray[0].material = material;
    }

    model->updateAll();

    return model;
}




ArticulatedModelRef createPlaneModel(const std::string& textureFile, float side, float tilePeriod) {
    Log::common()->printf("Creating plane...");
    ArticulatedModelRef model = ArticulatedModel::createEmpty();

    model->name = "Ground Plane";
    ArticulatedModel::Part& part = model->partArray.next();
    part.cframe = CoordinateFrame();
    part.name = "root";

    Log::common()->printf("1,\n");
    // There are N^2 blocks in this ground plane
    const int N = ceil(side);
    {
        const Vector3 scale   = Vector3(1, 1, 1) * side / N;
        const Vector3 center  = Vector3(1, 0, 1) * side / 2;
        const double texScale = 1 / tilePeriod;

        for (int z = 0; z <= N; ++z) {
            for (int x = 0; x <= N; ++x) {
                part.geometry.vertexArray.append(Vector3(x, 0, z) * scale - center);
                part.texCoordArray.append(Vector2(x - N/2.0, z - N/2.0) * texScale);
            }
        }
    }

    Log::common()->printf("2,\n");
    // Build triangle list
    ArticulatedModel::Part::TriList& triList = part.triListArray.next();
    triList.indexArray.clear();

    const int M = N + 1;
    for (int z = 0; z < N; ++z) {
        for (int x = 0; x < N; ++x) {
            triList.indexArray.append(x + z * M, x + (z + 1) * M, (x + 1) + (z + 1) * M);
            triList.indexArray.append((x + 1) + (z + 1) * M, (x + 1) + z * M, x + z * M);
        }
    }

    triList.twoSided = true;
    triList.material.emit.constant = Color3::black();

    triList.material.specular.constant = Color3::black();
    triList.material.specularExponent.constant = Color3::white() * 60;
    triList.material.reflect.constant = Color3::black();

    triList.material.diffuse.constant = Color3::white();
    triList.material.diffuse.map = Texture::fromFile(textureFile, TextureFormat::AUTO, Texture::TILE,
        Texture::TRILINEAR_MIPMAP, Texture::DIM_2D, 1.0f, Texture::DEPTH_NORMAL, 8.0);

    triList.computeBounds(part);
    Log::common()->printf("3,\n");

    part.indexArray = triList.indexArray;

    part.computeIndexArray();
    part.computeNormalsAndTangentSpace();
    part.updateVAR();
    part.updateShaders();

    Log::common()->printf("done\n");

    return model;
}


void conoid(
    const Vector3&              c1,
    const Vector2&              r1,
    const Vector3&              c2,
    const Vector2&              r2,
    const Vector3&              direction,
    const SuperShader::Material& material,
    ArticulatedModel::Part&     part) {

    // Index of the first new vertex
    int i0 = part.geometry.vertexArray.size();

    // Faces per side
    const int N = 16;

    // Sides
    {
        Array<Vector3>& vertex   = part.geometry.vertexArray;
        Array<Vector2>& texCoord = part.texCoordArray;

        // Go around a circle creating the side vertices. We intentionally double the 
        // first vertex to provide correct texture coordinates.
        for (int a = 0; a <= N; ++a) {
            float angle = ((float)a / N) * G3D_TWO_PI - G3D_PI;

            const Vector2 p(cos(angle), sin(angle));

            // Top
            const Vector3 v1 = Vector3(p * r1, 0) + c1;

            // Bottom
            const Vector3 v2 = Vector3(p * r2, 0) + c2;

            const Vector2 x1((float)a / N, 0.5);
            const Vector2 x2((float)a / N, 1.0);

            vertex.append(v1, v2);
            texCoord.append(x1, x2);
        }
    }

    // Top
    {
        Array<Vector3>& vertex   = part.geometry.vertexArray;
        Array<Vector2>& texCoord = part.texCoordArray;

        // Go around a circle creating the side vertices. We intentionally double the 
        // first vertex to provide correct texture coordinates.
        for (int a = 0; a <= N; ++a) {
            float angle = ((float)a / N) * G3D_TWO_PI - G3D_PI;

            const Vector2 p(cos(angle), sin(angle));
    
            vertex.append(Vector3(p * r1, 0) + c1);
            texCoord.append((Vector2(-p.x, p.y) / 4 + Vector2(0.25, 0.25)) + Vector2(0.5, 0.0));
        }
    }


    // Bottom
    {
        Array<Vector3>& vertex   = part.geometry.vertexArray;
        Array<Vector2>& texCoord = part.texCoordArray;

        // Go around a circle creating the side vertices. We intentionally double the 
        // first vertex to provide correct texture coordinates.
        for (int a = 0; a <= N; ++a) {
            float angle = ((float)a / N) * G3D_TWO_PI - G3D_PI;

            const Vector2 p(cos(angle), sin(angle));
    
            vertex.append(Vector3(p * r2, 0) + c2);
            texCoord.append((p / 4 + Vector2(0.25, 0.25)) + Vector2(0.0, 0.0));
        }
    }

    {
        // Build triangle list for sides
        ArticulatedModel::Part::TriList& triList = part.triListArray.next();
        triList.indexArray.clear();

        for (int a = 0; a < N; ++a) {
            int i = 2 * a + i0;
            triList.indexArray.append(i, i + 1, i + 3);
            triList.indexArray.append(i, i + 3, i + 2);
        }

        triList.twoSided = false;
        triList.material = material;

        triList.computeBounds(part);
        part.indexArray.append(triList.indexArray);
    }


    {
        // Build triangle list for top
        ArticulatedModel::Part::TriList& triList = part.triListArray.next();
        triList.indexArray.clear();

        int i = 2 * (N + 1) + i0;
        for (int a = 1; a < N; ++a) {
            triList.indexArray.append(i, i + a, i + a + 1);
        }

        triList.twoSided = false;
        triList.material = material;

        triList.computeBounds(part);
        part.indexArray.append(triList.indexArray);
    }

    {
        // Build triangle list for bottom
        ArticulatedModel::Part::TriList& triList = part.triListArray.next();
        triList.indexArray.clear();

        int i = i0 + 3 * (N + 1);
        for (int a = 1; a < N; ++a) {
            triList.indexArray.append(i, i + a + 1, i + a);
        }

        triList.twoSided = false;
        triList.material = material;

        triList.computeBounds(part);
        part.indexArray.append(triList.indexArray);
    }

    // Make a local reference frame
    CoordinateFrame cframe;
    {
        Vector3 Z = direction;
        Vector3 X = Vector3::unitX();
        if (G3D::abs(Z.dot(X)) > .95) {
            X = -Vector3::unitY();
        }
        X = (X - Z * (X.dot(Z))).direction();
        Vector3 Y = Z.cross(X);
        cframe.rotation.setColumn(0, X);
        cframe.rotation.setColumn(1, Y);
        cframe.rotation.setColumn(2, Z);
    }

//    cframe.rotation = Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(90));

    for (int i = i0; i < part.geometry.vertexArray.size(); ++i) {
        part.geometry.vertexArray[i] = cframe.pointToWorldSpace(part.geometry.vertexArray[i]);
    }

    /*
    part.computeIndexArray();
    part.computeNormalsAndTangentSpace();
    part.updateVAR();
    part.updateShaders();
    */
}


SuperShader::Material brass() {
    SuperShader::Material material;
    material.specular.constant = Color3::white();
    material.specularExponent.constant = Color3::white() * 60;
    material.reflect.constant = Color3::yellow() * 0.3f;
    material.diffuse.constant = Color3::orange() * 0.5f;
    return material;
}