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

#include "ScreenG3.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 Mover
{
    float value = 0;
    float destination = 0;
    float rate = 0.1;
    
public:
    void tick()
    {
        this->value = migrate(this->value, this->destination, this->rate);
    }
    
    float getValue()
    {
        return this->value;
    }
    
    void setDestination(float destination, float rate)
    {
        this->destination = destination;
        this->rate = rate;
    }

    void setValue(float value)
    {
        this->destination = value;
        this->value = value;
    }
};





/// A nonmoving box that reacts to objects passing through.
class SensingBox : public ITickableThing, public MeIVolumeListener
{
    MeGaud *gaud = 0;
    MeVolume *volume = 0;
    MeIMaterial *material = 0;
    
    MeVec4 colorCurrent;
    MeVec4 colorTarget;
    float colorMix = 1; // 1.0 is At Target.
    float colorRate = 1; // fractions of 1.0”’
    
public:
    METHING_NEWINSTANCE(SensingBox)
    
public:
    void setMaterial(MeIMaterial *material)
    {
        this->material = material;
    };

    MeGaud *getGaud()
    {
        if(!this->gaud)
        {
            MeVec4 xyzR = this->getValueVec4("cubeXyzR");
            MeVec3 radius(xyzR[3],xyzR[3],xyzR[3]);
            MeVec3 center(xyzR[0],xyzR[1],xyzR[2]);
            MeVec3 vLow = center - radius;
            MeVec3 vHigh = center + radius;
            MeGeometry *ge = makeBoxGeometry(vLow, vHigh);
            MePart *pa = new MePart(this->material, ge);
            this->gaud = new MeGaud();
            this->gaud->addPart(pa);
//            rw->addGaud(this->gaud);
            setGaudMatrix(this->gaud);
        }
        return this->gaud;
    }

    MeVolume *getVolume()
    {
        if(!this->volume)
        {
            this->volume = new MeVolume();
            MeVec4 xyzR = this->getValueVec4("cubeXyzR");
            this->volume->setCube(xyzR[0],xyzR[1],xyzR[2],xyzR[3]);
            
            int interest = this->getValueInt("interest");
            this->volume->setBit(interest);
            this->volume->addListener(this);
        }
        return this->volume;
    }
    
    void tick()
    {
        if(colorMix < 1.0)
        {
            this->colorMix += this->colorRate;
            MeVec3 color = mix(this->colorMix, this->colorCurrent, this->colorTarget);
            this->gaud->setPartAttribute("color", color);
        }
    }

    void configureKind(MeThingKind *kind)
    {
        kind->addParameterVec4("cubeXyzR");
        kind->addParameterInt("interest");
    }

    /// called when something passes through us
    void gotTouch(MeVolumeIntersection &intersection)
    {
    }
    
    void didTouch(MeVolumeIntersection &intersection)
    {
    }
    
    
    void movedTo(MeVolume *volume, MeVec3 where) {};

    void message(const MeVolumeMessage &message)
    {
        this->colorCurrent = message.v3;
        this->colorTarget = MeVec3(0.1, 0.1, 0.1);
        this->colorMix = 0;
        this->colorRate = rr(0.001, 0.03);
    }
};

/// A moving box that triggers sensors
class TriggeringBox : public ITickableThing, public MeIVolumeListener
{
    MeGaud *gaud = 0;
    MeVolume *volume = 0;
    MeIMaterial *material = 0;
    MeVec3 pos;
    MeMatrix4 ma;
    
    MeVec3 lissajousMultipliers;
    
    MeVec3 myColor;
    
    float t = 0;

public:
    METHING_NEWINSTANCE(TriggeringBox)
    
public:
    void setMaterial(MeIMaterial *material)
    {
        this->material = material;
    };
    
    MeGaud *getGaud()
    {
        if(!this->gaud)
        {
            this->lissajousMultipliers = MeVec3(rr(1,3), rr(1,3), rr(1,3));
            MeVec4 xyzR = this->getValueVec4("cubeXyzR");
            this->pos = xyzR;
            MeVec3 radius(xyzR[3],xyzR[3],xyzR[3]);
            MeGeometry *ge = makeBoxGeometry(-radius, +radius);
            MePart *pa = new MePart(this->material, ge);
            this->gaud = new MeGaud();
            this->gaud->addPart(pa);
            
            this->ma.lTranslate(this->pos);
            setGaudMatrix(this->gaud);
            this->gaud->setPartAttribute("color", MeVec4(1,1,1,1));
        }
        return this->gaud;
    }
    
    MeVolume *getVolume()
    {
        if(!this->volume)
        {
            this->myColor = MeVec3(rr(0,1),rr(0,1),rr(0,1));
            this->myColor.v[ir(0, 3)] = 1.0;
    
            this->volume = new MeVolume();
            MeVec4 xyzR = this->getValueVec4("cubeXyzR");
            this->volume->setCube(xyzR[0],xyzR[1],xyzR[2],xyzR[3]);
            int bit = this->getValueInt("bit");
            this->volume->setInterest(bit);
            this->volume->addListener(this);
        }
        return this->volume;
    }
    
    void setPos(MeVec3 newPos)
    {
        this->ma.lTranslate(newPos - this->pos);
        this->pos = newPos;
        setGaudMatrix(this->gaud,this->ma);
        this->volume->setCenter(newPos);
    }
    
    void tick()
    {
        t += 1.0 / 60.0;
        float ra = 20;
        float speed = .6;
        MeVec3 pos = sin(MeVec3(1,1,1) * this->lissajousMultipliers * t * speed) * ra;
        this->setPos(pos);
    }

    void configureKind(MeThingKind *kind)
    {
        kind->addParameterVec4("cubeXyzR");
        kind->addParameterInt("bit");
    }

    
    /// called when something passes through us
    void gotTouch(MeVolumeIntersection &intersection)
    {
        if(intersection.intersection == MVI_NONE)
            intersection.toucher->sendMessage(1, 2, this->myColor, NULL);
    }
    
    void didTouch(MeVolumeIntersection &intersection)
    {
    }
    void movedTo(MeVolume *volume, MeVec3 where) {};

    void message(const MeVolumeMessage &message)
    {
    }




};


static void makePoppingCubeWall(MeIMaterial *material, MeGeometry *slab, int xQuarterTurns, int yQuarterTurns, float zPush, MePart **partOut, MeMatrix4 *matrixOut)
{

    MeMatrix4 ma;
    ma.lRotateX(MePi * xQuarterTurns / 2.0);
    ma.lRotateY(MePi * yQuarterTurns / 2.0);
    ma.lTranslate(0, 0, zPush);
    

    MePart *pa = new MePart(material, slab);
    setPartMatrix(pa, ma);
    
    *partOut = pa;
    *matrixOut = ma;
}

/// A 6-faced cube which can expand open & shut
class PoppingCube : public ITickableThing
{
    MeGaud *gaud = 0;
    MePart *part1 = 0;
    MePart *part2 = 0;
    MePart *part3 = 0;
    MePart *part4 = 0;
    MePart *part5 = 0;
    MePart *part6 = 0;
    
    MeMatrix4 ma1;
    MeMatrix4 ma2;
    MeMatrix4 ma3;
    MeMatrix4 ma4;
    MeMatrix4 ma5;
    MeMatrix4 ma6;

    MeIMaterial *material = 0;
    MeVec3 pos;
    MeMatrix4 ma;
    MeVec3 myColor;
    
    Mover popDistanceMover;
    float popDistance = 0;
    
    float t = 0;
    
public:
    METHING_NEWINSTANCE(PoppingCube)
    
public:
    void setMaterial(MeIMaterial *material)
    {
        this->material = material;
    };
    
    MeGaud *getGaud()
    {
        if(!this->gaud)
        {
            MeVec4 xyzR = this->getValueVec4("cubeXyzR");

            float ra3 = xyzR[3];
            float wallThick = ra3 / 20;
            
            MeGeometry *slab = makeBoxGeometry(-ra3, -ra3, 0, ra3, ra3, wallThick);
            makePoppingCubeWall(material, slab, 0, 0, ra3, &this->part1, &this->ma1);
            makePoppingCubeWall(material, slab, 1, 0, ra3, &this->part2, &this->ma2);
            makePoppingCubeWall(material, slab, 2, 0, ra3, &this->part3, &this->ma3);
            makePoppingCubeWall(material, slab, 3, 0, ra3, &this->part4, &this->ma4);
            makePoppingCubeWall(material, slab, 0, 1, ra3, &this->part5, &this->ma5);
            makePoppingCubeWall(material, slab, 0, 3, ra3, &this->part6, &this->ma6);

            
            this->gaud = new MeGaud();
            this->gaud->addPart(this->part1);
            this->gaud->addPart(this->part2);
            this->gaud->addPart(this->part3);
            this->gaud->addPart(this->part4);
            this->gaud->addPart(this->part5);
            this->gaud->addPart(this->part6);
        }
        return this->gaud;
    }
    
    void pop(float popDistance)
    {
        this->popDistanceMover.setDestination(popDistance, 0.333);
    }
    
    void setPos(MeVec3 newPos)
    {
        this->ma.lTranslate(newPos - this->pos);
        this->pos = newPos;
        setGaudMatrix(this->gaud,this->ma);
    }
    
    void tick()
    {
        this->popDistanceMover.tick();
        
        float popDistance = this->popDistanceMover.getValue();

        float popDelta = popDistance - this->popDistance;
        this->popDistance = popDistance;
        
#define POPX(_ix) \
this->ma##_ix.lTranslate(0,0,popDelta); \
setPartMatrix(this->part##_ix, ma##_ix);
        
        POPX(1);
        POPX(2);
        POPX(3);
        POPX(4);
        POPX(5);
        POPX(6);
    }
    
    void configureKind(MeThingKind *kind)
    {
        kind->addParameterVec4("cubeXyzR");
        kind->addParameterInt("bit");
    }
};

static MeThingManager *buildThingManager()
{
    MeThingManager *tm = new MeThingManager();
    tm->addKind(new SensingBox());
    tm->addKind(new TriggeringBox());
    tm->addKind(new PoppingCube());
    return tm;
}


void ScreenG3::populateWorld()
{
    float ra = 20;
    
    for(int i = 0; i < 1200; i++)
    {
        MeThing *thing = tm->newInstance("SensingBox");
        this->things.push_back(thing);
        
        SensingBox *sb = (SensingBox *)thing;
        sb->setMaterial(material);
        
        MeVec4 cube(rr(-ra,ra), rr(-ra,ra), rr(-ra,ra), rr(0.5,2.5));
        thing->setValue("cubeXyzR", cube);
        vw->addVolume(sb->getVolume());
        
        MeGaud *gaud = sb->getGaud();
        this->renderWorld->addGaud(gaud);
    }
    
    for(int i = 0; i < 3; i++)
    {
        MeThing *thing = tm->newInstance("TriggeringBox");
        this->things.push_back(thing);
        
        thing->setValue("cubeXyzR", MeVec4(0,0,0,2));
        
        TriggeringBox *tb = (TriggeringBox *)thing;
        tb->setMaterial(material);
        
        vw->addVolume(tb->getVolume());
        MeGaud *gaud = tb->getGaud();
        this->renderWorld->addGaud(gaud);
    }
    
    {
        MeThing *thing = tm->newInstance("PoppingCube");
        this->things.push_back(thing);
        thing->setValue("cubeXyzR", MeVec4(0,0,0,ra * 2.5));
        PoppingCube *pc = (PoppingCube *)thing;
        pc->setMaterial(materialOpaque);
        
        MeGaud *gaud = pc->getGaud();
        this->renderWorld->addGaud(gaud);
        this->pc = pc;
    }
    
    // and the solid frame around it, five boxes at ra*3.
    {
        float ra3 = ra * 5;
        float ra3I = ra3 * 0.9;
        float wallThick = ra / 20;
        
        MeGeometry *ge1 = makeBoxGeometry(-ra3I, -ra3I, -ra3-wallThick,   ra3I, ra3I, -ra3, 0x000010);
        MeGeometry *ge2 = makeBoxGeometry(-ra3I, -ra3I, +ra3,   ra3I, ra3I, +ra3+wallThick, 0x000010);
        MeGeometry *ge3 = makeBoxGeometry(-ra3I, -ra3-wallThick, -ra3I,   ra3I, -ra3, ra3I, 0x000010);
        MeGeometry *ge4 = makeBoxGeometry(-ra3I, +ra3, -ra3I,   ra3I, +ra3+wallThick, ra3I, 0x000030);
        MeGeometry *ge5 = makeBoxGeometry(-ra3-wallThick, -ra3I, -ra3I,   -ra3, ra3I, ra3I, 0x000030);
        MeGeometry *ge6 = makeBoxGeometry(+ra3, -ra3I, -ra3I,   +ra3+wallThick, ra3I, ra3I, 0x000030);
        
        ge1->append(ge2);
        ge1->append(ge3);
        ge1->append(ge4);
        ge1->append(ge5);
        ge1->append(ge6);
        
        MePart *pa = new MePart(materialOpaque, ge1);
        MeGaud *ga = new MeGaud();
        ga->addPart(pa);
        //        rw->addGaud(ga);
        setGaudMatrix(ga);
    }
    
    
    
    
}



ScreenG3::ScreenG3() : Screen()
{
    this->tm = buildThingManager();
    
    this->materialOpaque = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
    this->material = loadMaterialGl("simpleTrans.vsh", "simpleTrans.fsh");
    this->material->setBlendMode(MBM_ADD);
    this->renderWorld = new MeRenderWorld();
    this->vw = new MeVolumeWorld();
    this->vw->spatialBinSize = 5;
    
    this->populateWorld();
//    populateWorld(this->vw, this->renderWorld, this->tm, this->material, this->materialOpaque, this->things);
    this->world = new MeWorld();
    
    this->makeSkybox();
    
    this->cameraMatrix.lTranslate(0, 10,10);
    this->cameraMatrix.lRotateX(- .6 * MePi);
    
    this->renderWorld->addStepClear("", 0xff000020);
    this->renderWorld->addStepDraw(this->materialSky, "");
    this->renderWorld->addStepDraw(this->materialOpaque, "");
    this->renderWorld->addStepDraw(this->material, "");
}

void ScreenG3::begin()
{
    
}
void ScreenG3::keyDown(int k)
{
    switch(k)
    {
        case 't':
            this->ticking = !this->ticking;
            break;
        case ' ':
            this->popDist = (this->popDist + 10) % 30;
            ((PoppingCube *)(this->pc))->pop(popDist);
            break;
        default:
            this->Screen::keyDown(k);
    }
}
void ScreenG3::keyUp(int k)
{
    switch(k)
    {
        default:
            this->Screen::keyUp(k);
    }
}
void ScreenG3::tick()
{
    if(this->ticking)
        for(MeThing *th : this->things)
        {
            ITickableThing *ct = (ITickableThing *)th;
            ct->tick();
        }
    
    
    this->world->updateFrame();
    this->Screen::tick();
    this->vw->getIntersections();
}
