//
//  ScreenG2.cpp
//  MetarealEngine
//
//  Created by David Van Brink on 1/29/15.
//  Copyright (c) 2015 David Van Brink. All rights reserved.
//

#include "ScreenG2.h"
/*
 what our THING needs to do:
  configuration
    startPos: vec4  xyz
    spinRate: float rpm
    shape:    int   0=cube, 1=octohedron
 
  other setup
    inject material to use
    (inject geometry resources to choose from?)

  action
    getGaud     create on first inquiry
    advanceTime update gaud
    shape-changed (must rejigger geometry)
 */



class CubeThing : public ITickableThing
{
    MeIMaterial *material = 0;
    MeGaud *gaud = 0;
    MeMatrix4 ma;
 
    MeVec3 pos;
    float rpm;
    int maIx1,maIx2,maIx3,maIx4;
    
    float orbit = rr(0.0008,0.007);

public:
    METHING_NEWINSTANCE(CubeThing)
    void configureKind(MeThingKind *kind)
    {
        kind->addParameterVec3("startPos");
        kind->addParameterVec3("vel");
        kind->addParameterFloat("rpm");
        kind->addParameterInt("shape");
    }
    
    void setMaterial(MeIMaterial *material)
    {
        this->material = material;
    }

    MeGaud *getGaud()
    {
        if(!this->gaud)
        {
            MeGeometry *ge = makeBoxGeometry(-1, -1, -1, rr(1,2), rr(1,2), rr(1,2));
            MePart *pa = new MePart(this->material, ge);
            this->gaud = new MeGaud();
            this->gaud->addPart(pa);
            this->pos = this->getValueVec4("startPos");

            MeVec3 posYz = pos;
            posYz.v[0] = 0;
            
            float r = posYz.length();
            float thre = 0.00001;
            if(r <  thre)
            {
                r = thre;
                this->pos += MeVec3(0, thre, thre);
            }
            float r2 = r + rr(10,15);
            MeVec3 xOuter(1.0, r2/r, r2/r);
            this->pos = this->pos * xOuter;
            float v = sqrtf(r2) / 10;
            this->orbit = v * this->orbit;
            
            this->ma.lTranslate(this->pos[0], this->pos[1], this->pos[2]);
            
            this->ma.lRotateX(rr(0,7));
            this->ma.lRotateY(rr(0,7));
            this->ma.lRotateZ(rr(0,7));
            
            setGaudMatrix(this->gaud, this->ma);
            
            std::vector<int> xformIndexes =
            this->gaud->getPartAttributeIndexes(std::vector<std::string>{"xform1","xform2","xform3","xform4"});
            this->maIx1 = xformIndexes[0];
            this->maIx2 = xformIndexes[1];
            this->maIx3 = xformIndexes[2];
            this->maIx4 = xformIndexes[3];
            
            this->rpm = this->getValueFloat("rpm");
            
        }
        return this->gaud;
    }
    
    void tick()
    {
        float rotationsPerMinute = this->rpm;//this->getValueFloat("rpm");
        float radiansPerTick = rotationsPerMinute * 2 * MePi * 1.0 / (60*60);
        this->ma.lRotateZ(radiansPerTick);
        this->ma.wRotateX(orbit);
        
        this->gaud->changesHold();
        this->gaud->setPartAttribute(this->maIx1, this->ma.column0());
        this->gaud->setPartAttribute(this->maIx2, this->ma.column1());
        this->gaud->setPartAttribute(this->maIx3, this->ma.column2());
        this->gaud->setPartAttribute(this->maIx4, this->ma.column3());
        this->gaud->changesGo();
    }
};


static float physG = 6.25e4;

class OrbitThing : public ITickableThing
{
    MeIMaterial *material = 0;
    MeGaud *gaud = 0;
    MeMatrix4 ma;
    
    MeVec3 pos;
    MeVec3 vel;
    
    float rpm; // tumblespeed

    int maIx1,maIx2,maIx3,maIx4;
    
    bool gotXformIndexes = false;
    
public:
    METHING_NEWINSTANCE(OrbitThing)

    void configureKind(MeThingKind *kind)
    {
        kind->addParameterVec3("startPos");
        kind->addParameterVec3("startVel");
        kind->addParameterFloat("rpm");
    }
    
    void setMaterial(MeIMaterial *material)
    {
        this->material = material;
    }
    
    MeGaud *getGaud()
    {
        if(!this->gaud)
        {
            this->pos = this->getValueVec3("startPos");
            this->vel = this->getValueVec3("startVel");
            this->rpm = this->getValueFloat("rpm");

            MeGeometry *ge = makeBoxGeometry(-1, -1, -1, rr(1,2), rr(1,2), rr(1,2));
            MePart *pa = new MePart(this->material, ge);
            this->gaud = new MeGaud();
            this->gaud->addPart(pa);
            
            // initial position
            this->ma.wTranslate(this->pos[0], this->pos[1], this->pos[2]);
            // tumble
            this->ma.lRotateX(rr(0,7));
            this->ma.lRotateY(rr(0,7));
            this->ma.lRotateZ(rr(0,7));
            
            setGaudMatrix(this->gaud, this->ma);
            
            
        }
        return this->gaud;
    }
    
    void ensureXformIndexes()
    {
        if(!this->gotXformIndexes)
        {
            std::vector<int> xformIndexes =
            this->gaud->getPartAttributeIndexes(std::vector<std::string>{"xform1","xform2","xform3","xform4"});
            this->maIx1 = xformIndexes[0];
            this->maIx2 = xformIndexes[1];
            this->maIx3 = xformIndexes[2];
            this->maIx4 = xformIndexes[3];
            this->gotXformIndexes = true;
        }
    }

    void tick()
    {
        this->ensureXformIndexes();

        float dT = 1.0 / 60.0;
        
        float tumbleRotationsPerMinute = this->rpm;
        float tumbleRotationsPerSecond = tumbleRotationsPerMinute / 60.0;
        float tumbleRotationsPerTick = tumbleRotationsPerSecond * dT;
        float tumbleRadiansPerTick = tumbleRotationsPerTick * 2 * MePi;
        this->ma.lRotateZ(tumbleRadiansPerTick);

        // Apply gravity, towards the origin.
        MeVec3 gravDir = (-this->pos).normalize();
        float r = this->pos.length();
        float gravA = physG / (r*r);
        this->vel += gravDir * (gravA * dT);

        // Apply current velocity
        MeVec3 newPos = this->pos + this->vel * dT;
        MeVec3 bump = newPos - this->pos;
        this->ma.wTranslate(bump);
        this->pos = newPos;
        
        this->gaud->changesHold();
        this->gaud->setPartAttribute(this->maIx1, this->ma.column0());
        this->gaud->setPartAttribute(this->maIx2, this->ma.column1());
        this->gaud->setPartAttribute(this->maIx3, this->ma.column2());
        this->gaud->setPartAttribute(this->maIx4, this->ma.column3());
        this->gaud->changesGo();
    }
};

static MeThingManager *buildThingManager()
{
    MeThingManager *tm = new MeThingManager();
    CubeThing *ct = new CubeThing();
    tm->addKind(ct);
    tm->addKind(new OrbitThing());
    return tm;
}

static void populateWorld(MeRenderWorld *rw, MeThingManager *tm, MeIMaterial *material, std::vector<MeThing *> &thingList)
{
    
    for(int i = 0; i < 15000; i++)
    {
        MeThing *thing = tm->newInstance("OrbitThing");
        MeVec3 startPos(rr(-100,100), rr(-100,100), rr(-10,10));
        thing->setValue("startPos", startPos);
        thing->setValue("startVel", MeVec3(rr(-30,30), rr(-30,30), rr(-30,30)));
        
        // give it a velocity perpendicular to its xy (orbit around Z) radius
        {
            MeVec3 vel = MeVec3(-startPos[1], startPos[0], 0.0).normalize() * MeVec3(startPos[0], startPos[1], 0).length() / 4;
            float variance = .2;
            vel += MeVec3(rr(-variance,+variance), rr(-variance,+variance), rr(-variance,+variance));
            thing->setValue("startVel", vel);
        }
        
        thing->setValue("rpm", rr(16,77));
        
        ITickableThing *ct = (ITickableThing *)thing;
        ct->setMaterial(material);
        
        MeGaud *gaud = ct->getGaud();
        rw->addGaud(gaud);
        
        thingList.push_back(ct);
        
//        ((CubeThing *)ct)->setRepeller(repeller);
    }
    

    
    
    
}

ScreenG2::ScreenG2() : Screen()
{
    this->tm = buildThingManager();
    
    this->material = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
    this->renderWorld = new MeRenderWorld();
    
    populateWorld(this->renderWorld, this->tm, this->material, this->things);
    this->world = new MeWorld();
    
    this->makeSkybox();
    
    this->cameraMatrix.lTranslate(0, 500, 200);
    this->cameraMatrix.lRotateX(- .6 * MePi);
    
    this->renderWorld->addStepClear("", 0x402020);
    this->renderWorld->addStepDraw(this->materialSky, "");
    this->renderWorld->addStepDraw(this->material, "");
}

void ScreenG2::begin()
{
    
}
void ScreenG2::keyDown(int k)
{
    switch(k)
    {
        case 't':
            this->ticking = !this->ticking;
            break;
        default:
            this->Screen::keyDown(k);
    }
}
void ScreenG2::keyUp(int k)
{
    switch(k)
    {
        default:
            this->Screen::keyUp(k);
    }
}
void ScreenG2::tick()
{
    if(this->ticking)
        for(MeThing *th : this->things)
        {
            ITickableThing *ct = (CubeThing *)th;
            ct->tick();
        }
    
    
    this->world->updateFrame();
    this->Screen::tick();
}
void ScreenG2::draw(MeIFrameBuffer *destination)
{
    this->renderWorld->clearRenderReport();
    this->renderWorld->draw(destination);
    MeRenderReport *rr = this->renderWorld->getRenderReport();
    if(rr->triangleCount > 100000000)
    printf("%d triangles\n", rr->triangleCount);
}
