// Script file for Mixxx Vestax Spin mapping
// Bill Good, Oct 31, 2010
// Parts of addButton and handleEvent are the work of Anders Gunnarsson

// move vu's to script? or fix bug in midi output saving

VestaxSpin = new function() {
    this.group = "[Master]";
    this.decks = [];
    this.buttons = [];
    this.controls = [];
    this.lights = [];
}

VestaxSpin.DECK_LIGHTS = [0x32, 0x35, 0x33, 0x24, 0x25, 0x46, 0x42, 0x21, 0x20, 0x29, 0x2a, 0x2b,
    0x2c, 0x2d];
VestaxSpin.MISC_LIGHTS = [0x26, 0x29, 0x28, 0x2a];
VestaxSpin.SCRATCH_TIMER_PERIOD = 20; // timer period in milliseconds,
// mixxx enforces a minimum of 20.

VestaxSpin.init = function(id) {
    VestaxSpin.decks = {
        "L": new VestaxSpin.Deck(1,"[Channel1]"),
        "R": new VestaxSpin.Deck(2,"[Channel2]")
    };

    VestaxSpin.addButton("songlist", new VestaxSpin.Button(2, 0x26, true), "handleSongList");
    VestaxSpin.lights["killwheels"] = new VestaxSpin.Light(2, 0x2a);
    VestaxSpin.lights["killwheels"].off();

    // clear everything
    for (var light in VestaxSpin.DECK_LIGHTS) {
        new VestaxSpin.Light(0, VestaxSpin.DECK_LIGHTS[light]).off();
        new VestaxSpin.Light(1, VestaxSpin.DECK_LIGHTS[light]).off();
        for (var j = 0; j < 10000000; ++j);
    }
    for (var light in VestaxSpin.MISC_LIGHTS) {
        new VestaxSpin.Light(2, VestaxSpin.MISC_LIGHTS[light]).off();
        for (var j = 0; j < 10000000; ++j);
    }
    // make the eqs do some pretty things
/*    var vu = [0x29, 0x2a, 0x2b, 0x2c, 0x2d];
    for (var light in vu) {
        new VestaxSpin.Light(0, vu[light]).on();
        new VestaxSpin.Light(1, vu[light]).on();
        for (var j = 0; j < 20000000; ++j);
    }
    for (var light in vu) {
        new VestaxSpin.Light(0, vu[light]).off();
        new VestaxSpin.Light(1, vu[light]).off();
        for (var j = 0; j < 20000000; ++j);
    }*/

}

VestaxSpin.shutdown = function(id) {
    // clear everything
    for (var light in VestaxSpin.DECK_LIGHTS) {
        new VestaxSpin.Light(0, VestaxSpin.DECK_LIGHTS[light]).off();
        new VestaxSpin.Light(1, VestaxSpin.DECK_LIGHTS[light]).off();
        for (var j = 0; j < 10000000; ++j);
    }
    for (var light in VestaxSpin.MISC_LIGHTS) {
        new VestaxSpin.Light(2, VestaxSpin.MISC_LIGHTS[light]).off();
        for (var j = 0; j < 10000000; ++j);
    }
}

VestaxSpin.GetDeck = function(group) {
    var groupToDeck = {
        "[Channel1]": "L",
        "[Channel2]": "R",
    };
    try {
        return this.decks[groupToDeck[group]];
    } catch (ex) {
        return null;
    }
}

VestaxSpin.addButton = function(buttonName, button, eventHandler) {
    button.group = this.group;
    button.parent = this;

    if (eventHandler) {
       var executionEnvironment = button;
       function handler(value) {
          try {
              executionEnvironment[eventHandler]();
          } catch (ex) {
              print("exception in executing handler for button " + buttonName + ": " + ex);
          }
       }
       button.handler = handler;
    }
    this.buttons[buttonName] = button;
    var control_map = this.controls[button.control];
    if (control_map) {
        control_map.push(button);
    } else {
        this.controls[button.control] = [button];
    }
}

VestaxSpin.handleEvent = function(channel, control, value, status, group) {
    var deck = VestaxSpin.GetDeck(group);
    if (deck != null) {
        deck.handleEvent(channel, control, value, status, group);
    }
    try {
        var buttons = VestaxSpin.controls[control];
    } catch (ex) {
        return;
    }
    for (var button in buttons) {
        buttons[button].handleEvent(value);
    }
}

VestaxSpin.ButtonState = {"released": 0x00, "pressed": 0x7F};

VestaxSpin.Button = function(channel, control, makeLight, lightControl) {
    this.channel = channel;
    this.control = control;
    this.group = null;
    this.state = VestaxSpin.ButtonState.released;
    this.handler = null;
    this.parent = null;
    if (makeLight) {
        if (lightControl) {
            this.light = new VestaxSpin.Light(this.channel, lightControl);
        } else {
            this.light = new VestaxSpin.Light(this.channel, this.control);
        }
    } else {
        this.light = null;
    }
}

VestaxSpin.LightState = {"on": 0x7f, "off": 0x00};

VestaxSpin.Light = function(channel, control) {
    this.channel = channel;
    this.control = control;
    this.state = VestaxSpin.LightState.off;
    this.on = function() {
        midi.sendShortMsg(0x90 + this.channel, this.control, VestaxSpin.LightState.on);
        this.state = VestaxSpin.LightState.on;
    }
    this.off = function() {
        midi.sendShortMsg(0x90 + this.channel, this.control, VestaxSpin.LightState.off);
        this.state = VestaxSpin.LightState.off;
    }
}

VestaxSpin.Button.prototype.handleEvent = function(value) {
    this.state = value;
    this.handler();
}

VestaxSpin.Deck = function(deckNum, group) {
    this.deckNum = deckNum;
    this.group = group;
    this.vinylMode = false;
    this.buttons = [];
    this.controls = [];
    this.lights = [];
    this.addButton("loop_open", new VestaxSpin.Button(deckNum-1, 0x21, true), "handleLoopOpen");
    this.addButton("loop_close", new VestaxSpin.Button(deckNum-1, 0x42, true), "handleLoopClose");
    this.addButton("sync", new VestaxSpin.Button(deckNum-1, 0x46, true), "handleSync");
    this.addButton("cue", new VestaxSpin.Button(deckNum-1, 0x35, true), "handleCue");
    this.addButton("cup", new VestaxSpin.Button(deckNum-1, 0x33, true), "handleCup");
    this.addButton("filter", new VestaxSpin.Button(deckNum-1, 0x24, true), "handleFilter");
    this.addButton("back", new VestaxSpin.Button(deckNum-1, 0x36, true, 0x32), "handleBack");
    this.addButton("rw", new VestaxSpin.Button(deckNum-1, 0x37, true, 0x35), "handleRW");
    this.addButton("ff", new VestaxSpin.Button(deckNum-1, 0x38, true, 0x33), "handleFF");
    this.addButton("wheeltouch", new VestaxSpin.Button(deckNum-1, 0x2e), "handleWheelTouch");
    // is this needed on the spin? I don't think we get a different control number
    // from touch if the scratch button light is active like on typhoon
    //this.addButton("wheeltouchfilter", new VestaxSpin.Button(deckNum-1, 0x2f), "handleWheelTouchFilter");
    this.addButton("jog", new VestaxSpin.Button(deckNum-1, 0x10), "handleWheel");
    // spin doesn't send different control number when the scratch button light
    // is enabled like the typhoon does
    //this.addButton("scratch", new VestaxSpin.Button(deckNum-1, 0x11), "handleScratch");

    this.lights["vu1"] = new VestaxSpin.Light(deckNum-1, 0x29);
    this.lights["vu2"] = new VestaxSpin.Light(deckNum-1, 0x2a);
    this.lights["vu3"] = new VestaxSpin.Light(deckNum-1, 0x2b);
    this.lights["vu4"] = new VestaxSpin.Light(deckNum-1, 0x2c);
    this.lights["vu5"] = new VestaxSpin.Light(deckNum-1, 0x2d);
}

VestaxSpin.Deck.prototype.addButton = VestaxSpin.addButton;

VestaxSpin.Deck.prototype.handleEvent = function(channel, control, value, status, group) {
    try {
        var buttons = this.controls[control];
    } catch (ex) {
        return;
    }
    if (buttons) {
        for (var button in buttons) {
            buttons[button].handleEvent(value);
        }
    }
}

VestaxSpin.Button.prototype.handleSongList = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue("[Playlist]", "SelectNextPlaylist", 1);
        this.light.on();
    } else {
        engine.setValue("[Playlist]", "SelectNextPlaylist", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleLoopOpen = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "loop_in", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "loop_in", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleLoopClose = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "loop_out", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "loop_out", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleSync = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "beatsync", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "beatsync", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleCue = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "cue_default", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "cue_default", 0);
        // shut off rw so that we don't get stuck in rw if the user lets go
        // of shift before letting go of cue/rw
        engine.setValue(this.group, "back", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleCup = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "cue_goto", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "cue_goto", 0);
        // shut off ff so that we don't get stuck in ff if the user lets go
        // of shift before letting go of cup/ff
        engine.setValue(this.group, "fwd", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleFilter = function() {
    if (this.state == VestaxSpin.ButtonState.released) return;
    if (this.parent.vinylMode == false) {
       this.parent.vinylMode = true;
       this.light.on();
    } else {
       this.parent.vinylMode = false;
       this.light.off();
        // kill scratch
        if (this.parent.buttons["wheeltouch"].timer > 0) {
            engine.stopTimer(this.parent.buttons["wheeltouch"].timer);
            this.parent.buttons["wheeltouch"].timer = 0;
        }
        engine.scratchDisable(this.parent.deckNum);
    }
}

VestaxSpin.Button.prototype.handleBack = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "play", 0);
        engine.setValue(this.group, "playposition", 0);
        this.light.on();
    } else {
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleRW = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "back", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "back", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleFF = function() {
    if (this.state == VestaxSpin.ButtonState.pressed) {
        engine.setValue(this.group, "fwd", 1);
        this.light.on();
    } else {
        engine.setValue(this.group, "fwd", 0);
        this.light.off();
    }
}

VestaxSpin.Button.prototype.handleWheelTouch = function() {
   if (this.parent.vinylMode && this.state == VestaxSpin.ButtonState.pressed) {
        if (this.timer > 0) {
            engine.stopTimer(this.timer);
            this.timer = 0;
        }
       engine.scratchEnable(this.parent.deckNum, 300, 33+(1.0/3), 1.0/8, (1.0/8)/32);
   } else {
        this.callback = function() {
            var last_fire = (new Date()).valueOf() - VestaxSpin.SCRATCH_TIMER_PERIOD;
            if (this.lastTick < last_fire) {
                engine.scratchDisable(this.parent.deckNum);
                engine.stopTimer(this.timer);
                this.timer = 0;
            }
        }
        this.timer = engine.beginTimer(VestaxSpin.SCRATCH_TIMER_PERIOD,
            "VestaxSpin.GetDeck(\"" + this.group + "\").buttons[\"wheeltouch\"].callback()");
   }
}

VestaxSpin.Button.prototype.handleWheel = function() {
    if (engine.getValue(this.group, "scratch2_enable")) {
        engine.scratchTick(this.parent.deckNum, this.state - 0x40);
        this.parent.buttons["wheeltouch"].lastTick = (new Date()).valueOf();
    } else {
        engine.setValue(this.group, "jog", this.state - 0x40);
    }
}
