#include "testApp.h"


extern Grid theGridView;
extern PresetManager presetManager;
extern EventLogger eventLogger;
extern Frequencer frequencer;

//DeviceID3523537000
//--------------------------------------------------------------
void testApp::setup(){
    paused = true;
	
    ofBackground( 0, 0, 0 );
    ofEnableAlphaBlending();
    //ofEnableSmoothing();

	// open an outgoing connection to HOST:PORT
	sender.setup( HOST, PORT );
    ofSetFrameRate(30);
    // reciever
    lastMoveTime = ofGetSystemTimeMicros();
    prevTouchX = 0;
    prevTouchY = 0;
    
    xLocked = false;
    yLocked = false;
    
    numActiveTouches = 0;
    touch0.setCoord(17./7., 2./3.);
    touch1.setCoord(10,20);
    
    prevTouch0.setCoord(1,2);
    prevTouch1.setCoord(10,20);
    prevDist = 10;
    slowFactor = 0.98;
    
    
    // the 5 harmonics for the frequencer
    freqIndexes.push_back(0);
    freqIndexes.push_back(4);
    freqIndexes.push_back(6);
    freqIndexes.push_back(7);
    freqIndexes.push_back(8);
    
    
    ofxiPhoneSetOrientation( OFXIPHONE_ORIENTATION_PORTRAIT ); // do this before setting up all the other objects
    
    theGridView.init();
    
    setupStandardGui();
    standardGUIShowing = false;
    standardGUI->setVisible(standardGUIShowing);
    
    setupZoomGui();
    zoomGUI->setVisible(!standardGUIShowing);
    
    // initial slider vals
    for(int i=0; i<10;i++){
        sliderVals.push_back(64);
    }

	int ticksPerBuffer = 8;	// 8 * 64 = buffer len of 512
	
	// setup the app core
	core.setup(2, 1, 44100, ticksPerBuffer);
    
	// setup OF sound stream
	ofSoundStreamSetup(2, 1, this, 44100, ofxPd::blockSize()*ticksPerBuffer, 3);
    
    presetManager.startupLoadAll();
    eventLogger.init();

    paused = false;
    
    
}
#pragma mark GUI
//--
float testApp::getWidth(){
    // depends on orientation
    return ofGetWidth();
    
}
float testApp::getHeight(){
    // depends on orientation
    return ofGetHeight();
    
}
//--------------------------------------------------------------
void testApp::setupZoomGui(){
    // width = height
    zoomGUI = new ofxUICanvas(getWidth()-200,0,190,300);
    zoomGUI->setTheme(OFX_UI_THEME_HACKER );
    
    zoomGUI->addLabelToggle("SWITCH VIEW", false);
    
    zoomGUI->addLabelButton("SAVE PRESET", false);

    
    zoomGUI->addLabelButton("ZOOM MIN", false);
    zoomGUI->addLabelButton("ZOOM MAX", false);
    zoomGUI->addLabelButton("QUESTIONS", false);
    
    ofAddListener(zoomGUI->newGUIEvent, this, &testApp::zoomGUIEvent);
}
//--------------------------------------------------------------
void testApp::zoomGUIEvent(ofxUIEventArgs &e){

    if(e.widget->getName() == "SWITCH VIEW")
    {
        // is toggle
        cout << "change VIEW\n";
        cout << "of width " << ofGetWidth() <<'\n';
        cout << "of height " << ofGetHeight() <<'\n';
        standardGUI->setVisible(!standardGUIShowing);
        standardGUIShowing = !standardGUIShowing;
        if(standardGUIShowing){
            // set the slider values to stuff got from zoomer
            sliderVals = theGridView.getParams();
            setAllGUISliders(sliderVals);
        }

    }else if(e.widget->getName() == "SAVE PRESET")
    {
        
        if(((ofxUIButton *)e.widget)->getValue()==0){ // touchup
            cout << "SAVE PRESET\n";
            presetManager.showNameDialog();

        }

    }else if(e.widget->getName() == "ZOOM MIN")
    {
        if(((ofxUIButton *)e.widget)->getValue()==0){ // touchup
        cout << "ZOOM MIN\n";
            theGridView.setMinZoom();
            eventLogger.logEvent(SET_MIN_ZOOM);
        }
    }else if(e.widget->getName() == "ZOOM MAX")
    {
        if(((ofxUIButton *)e.widget)->getValue()==0){ // touchup
            cout << "ZOOM MAX\n";
            theGridView.setMaxZoom();
            eventLogger.logEvent(SET_MAX_ZOOM);

        }
    }else if(e.widget->getName() == "QUESTIONS")
    {
        if(((ofxUIButton *)e.widget)->getValue()==0){ // touchup
            showQuestionnaire();
        }
    }else{
        cout << "GUI error :  unknown event recieved\n";
    }
    
}
//--------------------------------------------------------------
void testApp::showQuestionnaire(){
    // stop updating / drawing

    // if(eventLogger.questionnaireCompleted) return;
    
    paused = true;
    
    questionnaireViewController	= [[QuestionnaireViewController alloc] initWithNibName:@"QuestionnaireViewController" bundle:nil];
    [ofxiPhoneGetGLParentView() addSubview:questionnaireViewController.view];
	
    [questionnaireViewController setAppRef:(id)this];
    [questionnaireViewController show:(id)this];
    
    //IF THE VIEW IS HIDDEN LETS BRING IT BACK!
    //if( questionnaireViewController.view.hidden ){
    //    questionnaireViewController.view.hidden = NO;
    //}

}
//--------------------------------------------------------------
void testApp::questionnaireHidden(vector<int> answers){
    // send answers to server as json
    eventLogger.questionnaireAnswersObtained(answers);
    
    // set "we've done questionnaire" to true in event logger
    paused = false;
}
//--------------------------------------------------------------
void testApp::setupStandardGui(){
    float xInit = OFX_UI_GLOBAL_WIDGET_SPACING;
    float length = 256-xInit*2;
    
    
    float dim = 42;
    
    // LEFT GUI
    standardGUI = new ofxUICanvas(0,0,length+90,getHeight());
    
    
    ofxUILabelToggle * butt = standardGUI->addLabelToggle("LOCK SEQUENCE (X)", false);
    butt->setColorFill(ofColor(0,0,255));
    butt->setColorFillHighlight(ofColor(0,0,255));
    
    // Uh.. loop this
    for(int i = 1; i<=10;i++){
        stringstream ss;
        ss << "P" << i;
        ofxUISlider *slider;
        slider = (ofxUISlider *)standardGUI->addWidgetDown(new ofxUISlider(length,dim,0.0,127,64,ss.str()));
        slider->setDrawPadding(true);
        if(i <= 5){
            slider->setColorFill(ofColor(0,0,255));
            slider->setColorFillHighlight(ofColor(0,0,255));
        }else{
            slider->setColorFill(ofColor(255,0,0));
            slider->setColorFillHighlight(ofColor(255,0,0));
        }
        
        sliders.push_back(slider);
    }
    
    ofxUILabelToggle * butt2 = standardGUI->addLabelToggle("LOCK SYNTH (Y)", false);
    butt2->setColorFill(ofColor(255,0,0));
    butt2->setColorFillHighlight(ofColor(255,0,0));
    
    ofAddListener(standardGUI->newGUIEvent, this, &testApp::standardGUIEvent);
    standardGUI->loadSettings(ofxiPhoneGetDocumentsDirectory() + "guiSettings.xml");
    
}
//--------------------------------------------------------------
void testApp::standardGUIEvent(ofxUIEventArgs &e){
    if(!standardGUIShowing){
        cout << "GUI ERROR";
        return;
    }
    
    if(e.widget->getName() == "LOCK SEQUENCE (X)")
    {
        
        cout << "LOCK SEQUENCE (X)\n";
        // lock
        theGridView.shiftCentreToSnapped();
        xLocked = !xLocked;
        
    }else if(e.widget->getName() == "LOCK SYNTH (Y)")
    {
        
        cout << "LOCK SYNTH (Y)\n";
        // lock
        theGridView.shiftCentreToSnapped();
        if(((ofxUIButton *)e.widget)->getValue()){
            yLocked = true;
        }else{
            yLocked = false;
            
        }
    }
    
    // "normal" parameter changes
    for(int i = 1; i<=10;i++){
        stringstream ss;
        ss << "P" << i;
        string p = ss.str();
        
        if(e.widget->getName() == p)
        {
            //cout << "param change: " << p;
            ofxUISlider *slider = (ofxUISlider *) e.widget;
            sliderMoved(i-1,slider->getScaledValue()); // internal array 0 indexed
            
            
            
        }
    }
    
    // TODO reflect these changes in zoomer view? Or only when switching?
    
}
//--------------------------------------------------------------
void testApp::sliderMoved(int which, float value){
    // an update caused by slider view being touched
    sliderVals[which] = (int)value;
    theGridView.setParams(sliderVals);
    
    // TODO if <5 do frequencer stuff and send list to PD
    // if > 5 send control value to PD
    sendParametersToPD();
    
    eventLogger.logEvent(CHANGE_SLIDER, TwoVector(),0.0,which , value);
    
}
//--------------------------------------------------------------
void testApp::setAllGUISliders(vector<int> vals){
    // an update caused by zoomer view being moved
    for(int i = 0; i<10;i++){
        sliders[i]->setValue(vals[i]);
        sliderVals[i] = vals[i];
    }
    
}
//--------------------------------------------------------------
void testApp::sendParametersToPD(){
    // frequencer stuff to get 16 steps
    vector<double> vals;
    
    
    vals.push_back((sliderVals[0]+32)*8.); // DC offset
    for(int i=1; i<5;i++){
        vals.push_back((sliderVals[i] - 64)*2.);
    }

    vector<double> steps = frequencer.freqMagEdit(freqIndexes, vals);
    // send a list using the List object
    List seqSteps;
    
    seqSteps.addSymbol("seqSteps");
    for(int i=0; i < 16; i++){
        seqSteps.addFloat(round(steps[i])); // rounding here??
    }
    
    core.pd.sendList("fromOF", seqSteps);

    sendOscShape(sliderVals[5]);
    sendFiltType(sliderVals[6]);
    sendFiltFreq(sliderVals[7]);
    sendEnvShape(sliderVals[8]);
    sendModFreq(sliderVals[9]);
    
}
//--------------------------------------------------------------
#pragma mark STANDARD OF FUNCTIONS
//--------------------------------------------------------------
void testApp::update(){
	//we do a heartbeat on iOS as the phone will shut down the network connection to save power
	//this keeps the network alive as it thinks it is being used.
    if(paused) return;
    
	if( ofGetFrameNum() % 120 == 0 ){
		ofxOscMessage m;
		m.setAddress( "/misc/heartbeat" );
		m.addIntArg( ofGetFrameNum() );
		sender.sendMessage( m );
	}
    
    // continiue to move at velocity, unless snapped
    
    if (numActiveTouches == 0){ // no touches, use momentum
        // TODO set velocity to 0 when hits walls

        if(moveVel.norm() > 0.3){
            if(theGridView.snapped){
                // stop it, send snap event
                moveVel.setCoord(0.0,0.0);
            }else{
                theGridView.move(moveVel);
                moveVel = moveVel*slowFactor;
            }
         // and get new parameter values
            setAllGUISliders(theGridView.getParams());
            sendParametersToPD();
        }else if(moveVel.norm() > 0.01){ // and less than 0.3
            // stop it
            moveVel.setCoord(0.0,0.0);
            setAllGUISliders(theGridView.getParams());
            sendParametersToPD();
            eventLogger.logEvent(SCROLL_STOPPED, theGridView.getCoord() );
            
        }else{
            // stopped - do nothing
        }

    }
    // continiue to zoom at velocity
    if (numActiveTouches < 2 && abs(zoomVel)>0.001){
        theGridView.zoom(zoomVel + 1.0); // +1 because zoomVel factor is + or - , wheras zoom is a multiplier near 1 
        zoomVel = zoomVel*slowFactor;
    }

}
//--------------------------------------------------------------
void testApp::sendOSCParams(){
    
    vector<int> params = theGridView.getParams(); // FILTER HERE? NEED FLOATS...
    vector<int>::iterator iter = params.begin();
    
    ofxOscMessage m;
    m.setAddress( "p" );
    
    for(;iter < params.end();iter++){
        
        m.addFloatArg( *iter );
        
    }
    sender.sendMessage( m );
}
//--------------------------------------------------------------
void testApp::draw(){
    if(paused) return;
    if (standardGUIShowing){// ?
        ofSetColor(57, 57, 57,200);
        ofRect(0,0,getWidth(),getHeight());
    }
    theGridView.draw();

}

//--------------------------------------------------------------
void testApp::exit(){
    
    presetManager.exitAndSaveAll();
    eventLogger.exitAndSave();
    core.exit();
    
    delete standardGUI;
    delete zoomGUI;
    cout << "exit done \n";
}

//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
    if(standardGUIShowing){
        // check if in GUI area
        if(touch.x < 256) return;
    }
    
    numActiveTouches++;
    // absolute position doesn't matter
    // which one?
    if(touch.id == 0){
        touch0.setCoord(touch.x,touch.y);
        prevTouch0 = touch0;
    }else if(touch.id == 1){
        
        touch1.setCoord(touch.x,touch.y);
        prevTouch1 = touch1;
        
    }
    if(numActiveTouches == 1){
        moveVel.setCoord(0.0, 0.0);
        prevMove.setCoord(0.0, 0.0);
        prevMove2.setCoord(0.0, 0.0);
    }else if(numActiveTouches == 2){
        zoomVel = 0.0;
        prevZoom = 0.0;
        prevZoom2 = 0.0;
        double dist = touch1.distanceTo(touch0);
        prevDist = dist;
    }

}

//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
    
    //cout << "touch id  " << touch.id << "\n";
    //cout << "active touches  " << numActiveTouches << "\n";

    
    if(standardGUIShowing){
        // TODO check if in GUI area
        if(touch.x < 256) return;
    }
    // check if in other gui area
    if(touch.x > getWidth()-200 && touch.y < 300){
        return;
    }

    // which one? keep track of each touch point
    if(touch.id == 0){
        touch0.setCoord(touch.x,touch.y);
        
    }else if(touch.id == 1){
        
        touch1.setCoord(touch.x,touch.y);
    }

    if(numActiveTouches == 1){

        handleScroll();
    }else if(numActiveTouches == 2){
        handleZoom();
        
    }
    prevTouch0 = touch0;
   
    
}

//--------------------------------------------------------------
void testApp::handleScroll(){

    TwoVector move = touch0 - prevTouch0;
    if(yLocked){
        move.y = 0.0;
    }
    if(xLocked){
        move.x = 0.0;
    }

    // check time since last move - if
    unsigned int moveTime = ofGetSystemTimeMicros();
    if(moveTime - lastMoveTime > 100000){
        moveVel = TwoVector(); // zero
    }else{
        moveVel = (move*0.3 + prevMove*0.34 + prevMove2*0.38); // use the time
        
    }
    lastMoveTime = moveTime;
    

    prevMove2 = prevMove;
    prevMove = move;
    

    theGridView.move(move);
    
    // and get new parameter values
    setAllGUISliders(theGridView.getParams());
    sendParametersToPD();
}
//--------------------------------------------------------------
void testApp::handleZoom(){
    // work out change in difference
    double dist = touch1.distanceTo(touch0);
    double zoomFactor = prevDist/dist;
    
    //TODO check for sensible maximums, e.g. spurious touch data
    if(zoomFactor > 2.0 || zoomFactor < 0.5){
        cout << "Zoom too much!!!!" << zoomFactor;
        zoomFactor = 1.0;
    }
    
    zoomVel = (zoomFactor-1)*0.3 + prevZoom*0.34 + prevZoom2*0.38;
    prevZoom2 = prevZoom;
    prevZoom = (zoomFactor-1);
    
    theGridView.zoom(zoomFactor);
    
    prevDist = dist;

}
//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
    if(numActiveTouches > 0) numActiveTouches--; // dirty
    
    if(standardGUIShowing){
        // check if in GUI area
        if(touch.x < 256)
        
        return;
    }
    // check if in 

    // which one?
    if(touch.id == 0){
        // tricky situation - we tried to zoom but may have left non-move finger on
        prevTouch0.setCoord(touch.x,touch.y);
        
    }else if(touch.id == 1){
        

        prevTouch1.setCoord(0,0);
        
    }
    if(numActiveTouches == 0){
        // check time since last move
        // check time since last move - if
        unsigned int moveTime = ofGetSystemTimeMicros();
        if(moveTime - lastMoveTime > 100000){
            moveVel = TwoVector(); // zero
        }else{
            moveVel = (move*0.3 + prevMove*0.34 + prevMove2*0.38); // use the time
            
        }
        lastMoveTime = moveTime;
    }

}

//--------------------------------------------------------------
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
    // preset?

}

//--------------------------------------------------------------
void testApp::lostFocus(){

}

//--------------------------------------------------------------
void testApp::gotFocus(){

}

//--------------------------------------------------------------
void testApp::gotMemoryWarning(){

}

//--------------------------------------------------------------
void testApp::deviceOrientationChanged(int newOrientation){
/*
    cout << "orientation: " << newOrientation;

    if(newOrientation == 4){
        ofxiPhoneSetOrientation( OF_ORIENTATION_DEFAULT );
        
    }else if(newOrientation == 3){
        ofxiPhoneSetOrientation( OF_ORIENTATION_90_LEFT );
    }else if(newOrientation == 3){
            ofxiPhoneSetOrientation( OF_ORIENTATION_90_LEFT );
        }


    [ofxiPhoneGetGLView() updateDimensions];
*/
}


//--------------------------------------------------------------
void testApp::touchCancelled(ofTouchEventArgs& args){

}
//---------------------------------------------------------------
// AUDIO STUFF
//---------------------------------------------------------------

#pragma mark AUDIO STREAMS
//--------------------------------------------------------------
void testApp::audioReceived(float * input, int bufferSize, int nChannels) {
	core.audioReceived(input, bufferSize, nChannels);
}

void testApp::audioRequested(float * output, int bufferSize, int nChannels) {
	core.audioRequested(output, bufferSize, nChannels);
}
//---------------------------------------------------------------
#pragma mark UTILITIES

// 5hz cut off
const double fB[3] = {0.049489956268677,   0.098979912537354,   0.049489956268677};

const double fA[3] = {1.000000000000000,  -1.279632424997809,   0.477592250072517};

// 1hz cut off
//const double fB[3] = {0.002550535158536,   0.005101070317073,   0.002550535158536};

//const double fA[3] = {1.000000000000000,  -1.852146485395936,   0.862348626030081};


//a(1)*y(n) = b(1)*x(n) + b(2)*x(n-1) + ... + b(nb+1)*x(n-nb)- a(2)*y(n-1) - ... - a(na+1)*y(n-na)
//---------------------------------------------------------------
vector<float> testApp::vectorFilter(vector<float> newVec){
    static vector<float> x0(10,0);
    static vector<float> x1(10,0);
    static vector<float> x2(10,0);
    static vector<float> y0(10,0);
    static vector<float> y1(10,0);
    static vector<float> y2(10,0);
    
    x0 = newVec;
    
    // this low passes a bunch of params values all at once
    int sz =  newVec.size();
    for(int i=0; i<sz; i++){
        y0[i] = fB[0]*x0[i] + fB[1]*x1[i] + fB[2]*x2[i] - fA[1]*y1[i] - fA[2]*y2[i];
    }
    // shift
    x2 = x1;
    x1 = x0;
    y2 = y1;
    y1 = y0;
    
    return y0;
}
//---------------------------------------------------------------
void testApp::sendOscShape(int ctrlin){
    
    static int numpoints = 5;
    static int numcontrols = 5;
    //float values[points][controls] =
    float ctrlout[numcontrols];
    string ctrlName[5] = {"pWidth" , "sqVol", "sawVol", "sineVol", "FMAmt"};
    float values[5][5] =
        {{0.5, 0., 0., 1., 1.}, // 0
        {0.5, 0., 0., 1., 0.},  // 32 
        {0.5, 0., 1., 0., 0.},  // 64
        {0.5, 1., 1., 0., 0.},  // 96
        {0.01,1., 1., 0., 0.}}; // 127

    float fidx = (numpoints-1)*ctrlin/128.;
    int idx = floor(fidx);
    float frac = fidx - idx;
    for(int i=0; i < numcontrols; i++){
        ctrlout[i] = (1 - frac)*values[idx][i] + (frac)*values[idx+1][i];
        // send to PD
        List toPD;
        
        toPD.addSymbol(ctrlName[i]);
        toPD.addFloat(ctrlout[i]); // rounding here??

        core.pd.sendList("fromOF", toPD);
        //cout << ctrlName[i] << "sending" << ctrlout[i] << "\n";
    }
    
}
//---------------------------------------------------------------
void testApp::sendFiltType(int ctrlin){
    static int numpoints = 3;
    static int numcontrols = 4;
    //float values[points][controls] =
    float ctrlout[numcontrols];
    string ctrlName[4] = {"lpLev" , "bpLev", "hpLev", "reson"};
    float values[3][4] =
       {{1., 0., 0., 1}, // 0
        {0., 10., 0., 10},  // 64
        {0., 0., 1., 1}}; // 127
    
    float fidx = (numpoints-1)*ctrlin/128.;
    int idx = floor(fidx);
    float frac = fidx - idx;
    for(int i=0; i < numcontrols; i++){
        ctrlout[i] = (1 - frac)*values[idx][i] + (frac)*values[idx+1][i];
        // send to PD
        List toPD;
        
        toPD.addSymbol(ctrlName[i]);
        toPD.addFloat(ctrlout[i]); // rounding here??
        
        core.pd.sendList("fromOF", toPD);
        //cout << ctrlName[i] << "sending" << ctrlout[i] << "\n";
    }
}
//---------------------------------------------------------------
void testApp::sendFiltFreq(int ctrlin){
    List toPD;
    
    toPD.addSymbol("filtFreq");
    toPD.addFloat(ctrlin); 
    
    core.pd.sendList("fromOF", toPD);
}
//---------------------------------------------------------------
void testApp::sendEnvShape(int ctrlin){
    static int numpoints = 5;
    static int numcontrols = 3;
    //float values[points][controls] =
    float ctrlout[numcontrols];
    string ctrlName[3] = {"attack" , "decay", "sustain"};
    float values[5][3] =
        {{0., 0., 0.}, // 0
        {0., 0.5, 0.},  // 32
        {0.0, 1., 0.8},  // 64
        {0.99, 0.3, 0.},  // 96
        {0.3, 0.1, 0.}}; // 127
    
    float fidx = (numpoints-1)*ctrlin/128.;
    int idx = floor(fidx);
    float frac = fidx - idx;
    for(int i=0; i < numcontrols; i++){
        ctrlout[i] = (1 - frac)*values[idx][i] + (frac)*values[idx+1][i];
        // send to PD
        List toPD;
        
        toPD.addSymbol(ctrlName[i]);
        toPD.addFloat(ctrlout[i]); // rounding here??
        
        core.pd.sendList("fromOF", toPD);
        //cout << ctrlName[i] << "sending" << ctrlout[i] << "\n";
    }
}
//---------------------------------------------------------------
void testApp::sendModFreq(int ctrlin){
    float fm = ctrlin/127.;
    List toPD;
    
    toPD.addSymbol("FMFreq");
    toPD.addFloat(fm); // rounding here??
    
    core.pd.sendList("fromOF", toPD);
}
//---------------------------------------------------------------