#include "MonomeSerialDevice.h"
#include "debug.h"

MonomeSerialDevice::MonomeSerialDevice() {}

void MonomeSerialDevice::initialize() {
  active = false;
  isMonome = false;
  isGrid = true;
  rows = 0;
  columns = 0;
  encoders = 0;
  //clearQueue();
  clearAllLeds();
  arcDirty = false;
  gridDirty = false;
}

void MonomeSerialDevice::setupAsGrid(uint8_t _rows, uint8_t _columns) {
  initialize();
  active = true;
  isMonome = true;
  isGrid = true;
  rows = _rows;
  columns = _columns;
  gridDirty = true;
  debugfln(INFO, "GRID rows: %d columns %d", rows, columns);
}

void MonomeSerialDevice::setupAsArc(uint8_t _encoders) {
  initialize();
  active = true;
  isMonome = true;
  isGrid = false;
  encoders = _encoders;
  arcDirty = true;
  debugfln(INFO, "ARC encoders: %d", encoders);
}

void MonomeSerialDevice::setTiltActive(uint8_t sensor, bool active) {
  if (sensor < 4) {
    tiltActive[sensor] = active;
  }
}

void MonomeSerialDevice::sendTiltEvent(uint8_t sensor, int16_t x, int16_t y, int16_t z) {
    if (sensor < 4 && tiltActive[sensor]) {
        lastTiltX[sensor] = x;
        lastTiltY[sensor] = y;
        lastTiltZ[sensor] = z;

        Serial.write((uint8_t)0x81);  // tiltイベントのプレフィックス
        Serial.write(sensor);
        Serial.write((uint8_t)(x >> 8));
        Serial.write((uint8_t)(x & 0xFF));
        Serial.write((uint8_t)(y >> 8));
        Serial.write((uint8_t)(y & 0xFF));
        Serial.write((uint8_t)(z >> 8));
        Serial.write((uint8_t)(z & 0xFF));
    }
}


void MonomeSerialDevice::getDeviceInfo() {
  //debugln(INFO, "MonomeSerialDevice::getDeviceInfo");
  Serial.write(uint8_t(0));
}

void MonomeSerialDevice::poll() {
  //while (isMonome && Serial.available()) { processSerial(); };
  if (Serial.available()) {
    processSerial();
  }
  //Serial.println("processSerial");
}


void MonomeSerialDevice::setAllLEDs(int value) {
  for (int i = 0; i < MAXLEDCOUNT; i++) leds[i] = value;
}

void MonomeSerialDevice::setGridLed(uint8_t x, uint8_t y, uint8_t level) {
  //    int index = x + (y * columns);
  //    if (index < MAXLEDCOUNT) leds[index] = level;

  if (x < columns && y < rows) {
    uint32_t index = y * columns + x;
    leds[index] = level;
  }
  //debugfln(INFO, "LED index: %d x %d y %d", index, x, y);
}

void MonomeSerialDevice::clearGridLed(uint8_t x, uint8_t y) {
  setGridLed(x, y, 0);
  //Serial.println("clearGridLed");
}

void MonomeSerialDevice::setArcLed(uint8_t enc, uint8_t led, uint8_t level) {
  int index = led + (enc << 6);
  if (index < MAXLEDCOUNT) leds[index] = level;
  //Serial.println("setArcLed");
}

void MonomeSerialDevice::clearArcLed(uint8_t enc, uint8_t led) {
  setArcLed(enc, led, 0);
  //Serial.println("clearArcLed");
}

void MonomeSerialDevice::clearAllLeds() {
  for (int i = 0; i < MAXLEDCOUNT; i++) leds[i] = 0;
  //Serial.println("clearAllLeds");
}

void MonomeSerialDevice::clearArcRing(uint8_t ring) {
  for (int i = ring << 6, upper = i + 64; i < upper; i++) leds[i] = 0;
  //Serial.println("clearArcRing");
}

void MonomeSerialDevice::refreshGrid() {
  gridDirty = true;
  //Serial.println("refreshGrid");
}

void MonomeSerialDevice::refreshArc() {
  arcDirty = true;
  //Serial.println("refreshArc");
}

void MonomeSerialDevice::sendSysSize() {
  Serial.write((uint8_t)0x05);  // system / get grid size
  Serial.write((uint8_t)columns);
  Serial.write((uint8_t)rows);
}

void MonomeSerialDevice::sendSysRotation() {
  // Note: The original Monome protocol doesn't have a direct command for rotation.
  // This is a custom implementation. You may need to adjust how the host interprets this.
  Serial.write((uint8_t)0x0F);  // Using 0x0F (system / report firmware version) as a custom command
  Serial.write((uint8_t)0xF0);  // Custom identifier for rotation
  Serial.write((uint8_t)gridRotation);
}

void MonomeSerialDevice::refresh() {
  /*
    uint8_t buf[35];
    int ind, led;

    if (gridDirty) {
        //Serial.println("gridDirty");
        buf[0] = 0x1A;
        buf[1] = 0;
        buf[2] = 0;

        ind = 3;
        for (int y = 0; y < 8; y++)
            for (int x = 0; x < 8; x += 2) {
                led = (y << 4) + x;
                buf[ind++] = (leds[led] << 4) | leds[led + 1];
            }
        Serial.write(buf, 35);
        
        ind = 3;
        buf[1] = 8;
        for (int y = 0; y < 8; y++)
            for (int x = 8; x < 16; x += 2) {
                led = (y << 4) + x;
                buf[ind++] = (leds[led] << 4) | leds[led + 1];
            }
        Serial.write(buf, 35);
        
        ind = 3;
        buf[1] = 0;
        buf[2] = 8;
        for (int y = 8; y < 16; y++)
            for (int x = 0; x < 8; x += 2) {
                led = (y << 4) + x;
                buf[ind++] = (leds[led] << 4) | leds[led + 1];
            }
        Serial.write(buf, 35);

        ind = 3;
        buf[1] = 8;
        for (int y = 8; y < 16; y++)
            for (int x = 8; x < 16; x += 2) {
                led = (y << 4) + x;
                buf[ind++] = (leds[led] << 4) | leds[led + 1];
            }
        Serial.write(buf, 35);
        
        gridDirty = false;
    }

    if (arcDirty) {
        //Serial.print("arcDirty");
        buf[0] = 0x92;

        buf[1] = 0;
        ind = 2;
        for (led = 0; led < 64; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);
        
        buf[1] = 1;
        ind = 2;
        for (led = 64; led < 128; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);

        buf[1] = 2;
        ind = 2;
        for (led = 128; led < 192; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);
        
        buf[1] = 3;
        ind = 2;
        for (led = 192; led < 256; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);
        
        buf[1] = 4;
        ind = 2;
        for (led = 256; led < 320; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);

        buf[1] = 5;
        ind = 2;
        for (led = 320; led < 384; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);

        buf[1] = 6;
        ind = 2;
        for (led = 384; led < 448; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);

        buf[1] = 7;
        ind = 2;
        for (led = 448; led < 512; led += 2)
            buf[ind++] = (leds[led] << 4) | leds[led + 1];
        Serial.write(buf, 34);

        arcDirty = 0;
    }
*/
}

void MonomeSerialDevice::processSerial() {
  uint8_t identifierSent;  // command byte sent from controller to matrix
  uint8_t index, readX, readY, readN, readA;
  uint8_t dummy, gridNum, deviceAddress;  // for reading in data not used by the matrix
  uint8_t n, x, y, z, i;
  uint8_t intensity = 15;
  uint8_t gridKeyX;
  uint8_t gridKeyY;
  int8_t delta;
  uint8_t gridX = columns;  // Will be either 8 or 16
  uint8_t gridY = rows;
  uint8_t numQuads = columns / rows;

  // get command identifier: first byte of packet is identifier in the form: [(a << 4) + b]
  // a = section (ie. system, key-grid, digital, encoder, led grid, tilt)
  // b = command (ie. query, enable, led, key, frame)

  identifierSent = Serial.read();

  switch (identifierSent) {
    case 0x00:  // device information
                // [null, "led-grid", "key-grid", "digital-out", "digital-in", "encoder", "analog-in", "analog-out", "tilt", "led-ring"]
      //Serial.println("0x00 system / query ----------------------");
      Serial.write((uint8_t)0x00);      // action: response, 0x00 = system
      Serial.write((uint8_t)0x01);      // section id, 1 = led-grid, 2 = key-grid, 5 = encoder/arc
      Serial.write((uint8_t)numQuads);  // one Quad is 64 buttons

      Serial.write((uint8_t)0x00);  // send again with 2 = key-grid
      Serial.write((uint8_t)0x02);  //
      Serial.write((uint8_t)numQuads);

      break;

    case 0x01:                      // system / ID
      Serial.write((uint8_t)0x01);  // action: response, 0x01
      for (i = 0; i < 32; i++) {    // has to be 32
        if (i < deviceID.length()) {
          Serial.write(deviceID[i]);
        } else {
          Serial.write((uint8_t)0x00);
        }
      }
      break;

    case 0x02:  // system / write ID
      //Serial.println("0x02");
      for (int i = 0; i < 32; i++) {  // has to be 32
        deviceID[i] = Serial.read();
      }
      break;

    case 0x03:  // system / report grid offset
      //Serial.println("0x03");
      Serial.write((uint8_t)0x02);  // system / request grid offset - bytes: 1 - [0x03]
      Serial.write((uint8_t)0x01);
      Serial.write((uint8_t)0);  // x offset - could be 0 or 8  ### NEEDS grid size variable
      Serial.write((uint8_t)0);  // y offset
      break;

    case 0x04:  // system / report ADDR
      //Serial.println("0x04");
      gridNum = Serial.read();  // grid number
      readX = Serial.read();    // x offset
      readY = Serial.read();    // y offset
      break;

    case 0x05:  // _SYS_GET_GRID_SIZE
      //Serial.println("0x05");
      Serial.write((uint8_t)0x03);   // system / request grid size
      Serial.write((uint8_t)gridX);  // gridX
      Serial.write((uint8_t)gridY);  // gridY
      break;

    case 0x06:
      readX = Serial.read();  // system / set grid size - ignored
      readY = Serial.read();
      break;

    case 0x07:
      break;  // I2C get addr (scan) - ignored

    case 0x08:
      deviceAddress = Serial.read();  // I2C set addr - ignored
      dummy = Serial.read();
      break;


    case 0x0F:  // system / report firmware version
      // Serial.println("0x0F");
      for (int i = 0; i < 8; i++) {  // 8 character string
                                     //Serial.print(Serial.read());
      }
      break;


      // 0x10-0x1F are for an LED Grid Control.  All bytes incoming, no responses back

    case 0x10:  // /prefix/led/set x y [0/1]  / led off
      readX = Serial.read();
      readY = Serial.read();
      setGridLed(readX, readY, 0);
      break;

    case 0x11:  // /prefix/led/set x y [0/1]   / led on
      readX = Serial.read();
      readY = Serial.read();
      setGridLed(readX, readY, 15);  // need global brightness variable?
      break;

    case 0x12:  //  /prefix/led/all [0/1]  / all off
      clearAllLeds();
      break;

    case 0x13:  //  /prefix/led/all [0/1] / all on
      setAllLEDs(15);
      break;

    case 0x14:  // /prefix/led/map x y d[8]  / map (frame)
      readX = Serial.read();
      while (readX > 16) { readX += 16; }  // hacky shit to deal with negative numbers from rotation
      readX &= 0xF8;                       // floor the offset to 0 or 8

      readY = Serial.read();               // y offset
      while (readY > 16) { readY += 16; }  // hacky shit to deal with negative numbers from rotation
      readY &= 0xF8;                       // floor the offset to 0 or 8

      for (y = 0; y < 8; y++) {     // each i will be a row
        intensity = Serial.read();  // read one byte of 8 bits on/off

        for (x = 0; x < 8; x++) {         // for 8 LEDs on a row
          if ((intensity >> x) & 0x01) {  // if intensity bit set, light led full brightness
            setGridLed(readX + x, readY + y, 15);
          } else {
            setGridLed(readX + x, readY + y, 0);
          }
        }
      }
      break;

    case 0x15:                             //  /prefix/led/row x y d
      readX = Serial.read();               // led-grid / set row
      while (readX > 16) { readX += 16; }  // hacky shit to deal with negative numbers from rotation
      readX &= 0xF8;                       // floor the offset to 0 or 8

      readY = Serial.read();      //
      intensity = Serial.read();  // read one byte of 8 bits on/off

      for (x = 0; x < 8; x++) {         // for the next 8 lights in row
        if ((intensity >> x) & 0x01) {  // if intensity bit set, light led full brightness
          setGridLed(readX + x, readY, 15);
        } else {
          setGridLed(readX + x, readY, 0);
        }
      }

      break;

    case 0x16:                //  /prefix/led/col x y d
      readX = Serial.read();  // led-grid / column set

      readY = Serial.read();
      while (readY > 16) { readY += 16; }  // hacky shit to deal with negative numbers from rotation
      readY &= 0xF8;                       // floor the offset to 0 or 8

      intensity = Serial.read();  // read one byte of 8 bits on/off

      for (y = 0; y < 8; y++) {         // for the next 8 lights in column
        if ((intensity >> y) & 0x01) {  // if intensity bit set, light led full brightness
          setGridLed(readX, readY + y, 15);
        } else {
          setGridLed(readX, readY + y, 0);
        }
      }

      break;

    case 0x17:                    //  /prefix/led/intensity i
      intensity = Serial.read();  // set brightness for entire grid
      // this is probably not right
      setAllLEDs(intensity);

      break;

    case 0x18:                    //  /prefix/led/level/set x y i
      readX = Serial.read();      // led-grid / set LED intensity
      readY = Serial.read();      // read the x and y coordinates
      intensity = Serial.read();  // read the intensity
      setGridLed(readX, readY, intensity);
      break;

    case 0x19:                    //  /prefix/led/level/all s
      intensity = Serial.read();  // set all leds
      setAllLEDs(intensity);
      break;

    case 0x1A:                             //   /prefix/led/level/map x y d[64]
                                           // set 8x8 block
      readX = Serial.read();               // x offset
      while (readX > 16) { readX += 16; }  // hacky shit to deal with negative numbers from rotation
      readX &= 0xF8;                       // floor the offset to 0 or 8
      readY = Serial.read();               // y offset
      while (readY > 16) { readY += 16; }  // hacky shit to deal with negative numbers from rotation
      readY &= 0xF8;                       // floor the offset to 0 or 8

      z = 0;
      for (y = 0; y < 8; y++) {
        for (x = 0; x < 8; x++) {
          if (z % 2 == 0) {
            intensity = Serial.read();
            if (((intensity >> 4) & 0x0F) > variMonoThresh) {  // even bytes, use upper nybble
              setGridLed(readX + x, readY + y, (intensity >> 4) & 0x0F);
            } else {
              setGridLed(readX + x, readY + y, 0);
            }
          } else {
            if ((intensity & 0x0F) > variMonoThresh) {  // odd bytes, use lower nybble
              setGridLed(readX + x, readY + y, intensity & 0x0F);
            } else {
              setGridLed(readX + x, readY + y, 0);
            }
          }
          z++;
        }
      }
      /*
          } else {
            for (int q = 0; q<32; q++){
              Serial.read();
            }
          }*/
      break;

    case 0x1B:                             // /prefix/led/level/row x y d[8]
      readX = Serial.read();               // x offset
      while (readX > 16) { readX += 16; }  // hacky shit to deal with negative numbers from rotation
      readX &= 0xF8;                       // floor the offset to 0 or 8
      readY = Serial.read();               // y offset
      while (readY > 16) { readY += 16; }  // hacky shit to deal with negative numbers from rotation
      readY &= 0xF8;                       // floor the offset to 0 or 8
      for (x = 0; x < 8; x++) {
        if (x % 2 == 0) {
          intensity = Serial.read();
          if ((intensity >> 4 & 0x0F) > variMonoThresh) {  // even bytes, use upper nybble
            setGridLed(readX + x, readY, (intensity >> 4) & 0x0F);
          } else {
            setGridLed(readX + x, readY, 0);
          }
        } else {
          if ((intensity & 0x0F) > variMonoThresh) {  // odd bytes, use lower nybble
            setGridLed(readX + x, readY, intensity & 0x0F);
          } else {
            setGridLed(readX + x, readY, 0);
          }
        }
      }
      break;

    case 0x1C:                             // /prefix/led/level/col x y d[8]
      readX = Serial.read();               // x offset
      while (readX > 16) { readX += 16; }  // hacky shit to deal with negative numbers from rotation
      readX &= 0xF8;                       // floor the offset to 0 or 8
      readY = Serial.read();               // y offset
      while (readY > 16) { readY += 16; }  // hacky shit to deal with negative numbers from rotation
      readY &= 0xF8;                       // floor the offset to 0 or 8
      for (y = 0; y < 8; y++) {
        if (y % 2 == 0) {
          intensity = Serial.read();
          if ((intensity >> 4 & 0x0F) > variMonoThresh) {  // even bytes, use upper nybble
            setGridLed(readX, readY + y, (intensity >> 4) & 0x0F);
          } else {
            setGridLed(readX, readY + y, 0);
          }
        } else {
          if ((intensity & 0x0F) > variMonoThresh) {  // odd bytes, use lower nybble
            setGridLed(readX, readY + y, intensity & 0x0F);
          } else {
            setGridLed(readX, readY + y, 0);
          }
        }
      }
      break;



      // 0x20 and 0x21 are for a Key inputs (grid) - see readKeys() function

    case 0x20:
      /*
             0x20 key-grid / key up
             bytes: 3
             structure: [0x20, x, y]
             description: key up at (x,y)
             */

      gridKeyX = Serial.read();
      gridKeyY = Serial.read();
      addGridEvent(gridKeyX, gridKeyY, 0);
      /*
            Serial.print("grid key: ");
            Serial.print(gridKeyX);
            Serial.print(" ");
            Serial.print(gridKeyY);
            Serial.print(" up - ");
            */
      break;

    case 0x21:
      /*
             0x21 key-grid / key down
             bytes: 3
             structure: [0x21, x, y]
             description: key down at (x,y)
             */
      gridKeyX = Serial.read();
      gridKeyY = Serial.read();
      addGridEvent(gridKeyX, gridKeyY, 1);
      /*
            Serial.print("grid key: ");
            Serial.print(gridKeyX);
            Serial.print(" ");
            Serial.print(gridKeyY);
            Serial.print(" dn - ");
            */
      break;

    // 0x5x are encoder
    case 0x50:
      // bytes: 3
      // structure: [0x50, n, d]
      // n = encoder number
      //  0-255
      // d = delta
      //  (-128)-127 (two's comp 8 bit)
      // description: encoder position change

      index = Serial.read();
      delta = Serial.read();
      addArcEvent(index, delta);
      /*
            Serial.print("Encoder: ");
            Serial.print(index);
            Serial.print(" : ");
            Serial.print(delta);
            Serial.println();
            */
      break;

    case 0x51:  // /prefix/enc/key n (key up)
      // Serial.println("0x51");
      n = Serial.read();
      /*
            Serial.print("key: ");
            Serial.print(n);
            Serial.println(" up");
            */
      // bytes: 2
      // structure: [0x51, n]
      // n = encoder number
      //  0-255
      // description: encoder switch up
      break;

    case 0x52:  // /prefix/enc/key n (key down)
      // Serial.println("0x52");
      n = Serial.read();
      /*
            Serial.print("key: ");
            Serial.print(n);
            Serial.println(" down");
            */
      // bytes: 2
      // structure: [0x52, n]
      // n = encoder number
      //  0-255
      // description: encoder switch down
      break;
    case 0x80:  // tilt / set active
      {
        uint8_t sensor = Serial.read();
        uint8_t active = Serial.read();
        setTiltActive(sensor, active == 1);

        // send
        Serial.write((uint8_t)0x80);
        Serial.write(sensor);
        Serial.write(active);
      }
      break;

    case 0x81:  // tilt data request
      {
        uint8_t sensor = Serial.read();
        if (sensor < 4 && tiltActive[sensor]) {
          // recent data send
          sendTiltEvent(sensor, lastTiltX[sensor], lastTiltY[sensor], lastTiltZ[sensor]);
        }
      }
      break;

    // 0x90 variable 64 LED ring
    case 0x90:
      //pattern:  /prefix/ring/set n x a
      //desc:   set led x of ring n to value a
      //args:   n = ring number
      //      x = led number
      //      a = value (0-15)
      //serial:   [0x90, n, x, a]
      readN = Serial.read();
      readX = Serial.read();
      readA = Serial.read();
      //led_array[readN][readX] = readA;
      setArcLed(readN, readX, readA);
      break;

    case 0x91:
      //pattern:  /prefix/ring/all n a
      //desc:   set all leds of ring n to a
      //args:   n = ring number
      //      a = value
      //serial:   [0x91, n, a]
      readN = Serial.read();
      readA = Serial.read();
      for (int q = 0; q < 64; q++) {
        setArcLed(readN, q, readA);
        //led_array[readN][q]=readA;
      }
      break;

    case 0x92:
      //pattern:  /prefix/ring/map n d[32]
      //desc:   set leds of ring n to array d
      //args:   n = ring number
      //      d[32] = 64 states, 4 bit values, in 32 consecutive bytes
      //      d[0] (0:3) value 0
      //      d[0] (4:7) value 1
      //      d[1] (0:3) value 2
      //      ....
      //      d[31] (0:3) value 62
      //      d[31] (4:7) value 63
      //serial:   [0x92, n d[32]]
      readN = Serial.read();
      for (y = 0; y < 64; y++) {
        if (y % 2 == 0) {
          intensity = Serial.read();
          if ((intensity >> 4 & 0x0F) > 0) {  // even bytes, use upper nybble
            //led_array[readN][y] = (intensity >> 4 & 0x0F);
            setArcLed(readN, y, (intensity >> 4 & 0x0F));
          } else {
            //led_array[readN][y]=0;
            setArcLed(readN, y, 0);
          }
        } else {
          if ((intensity & 0x0F) > 0) {  // odd bytes, use lower nybble
            //led_array[readN][y] = (intensity & 0x0F);
            setArcLed(readN, y, (intensity & 0x0F));
          } else {
            //led_array[readN][y]=0;
            setArcLed(readN, y, 0);
          }
        }
      }
      break;

    case 0x93:
      //pattern:  /prefix/ring/range n x1 x2 a
      //desc:   set leds inclusive from x1 and x2 of ring n to a
      //args:   n = ring number
      //      x1 = starting position
      //      x2 = ending position
      //      a = value
      //serial:   [0x93, n, x1, x2, a]
      readN = Serial.read();
      readX = Serial.read();  // x1
      readY = Serial.read();  // x2
      readA = Serial.read();
      //memset(led_array[readN],0,sizeof(led_array[readN]));

      if (readX < readY) {
        for (y = readX; y < readY; y++) {
          //led_array[readN][y] = readA;
          setArcLed(readN, y, readA);
        }
      } else {
        // wrapping?
        for (y = readX; y < 64; y++) {
          //led_array[readN][y] = readA;
          setArcLed(readN, y, readA);
        }
        for (x = 0; x < readY; x++) {
          //led_array[readN][x] = readA;
          setArcLed(readN, y, readA);
        }
      }
      //note:   set range x1-x2 (inclusive) to a. wrapping supported, ie. set range 60,4 would set values 60,61,62,63,0,1,2,3,4.
      // always positive direction sweep. ie. 4,10 = 4,5,6,7,8,9,10 whereas 10,4 = 10,11,12,13...63,0,1,2,3,4
      break;

    default:
      break;
  }
}

void MonomeEventQueue::addGridEvent(uint8_t x, uint8_t y, uint8_t pressed) {
  if (gridEventCount >= MAXEVENTCOUNT) return;
  uint8_t ind = (gridFirstEvent + gridEventCount) % MAXEVENTCOUNT;
  gridEvents[ind].x = x;
  gridEvents[ind].y = y;
  gridEvents[ind].pressed = pressed;
  gridEventCount++;
}

bool MonomeEventQueue::gridEventAvailable() {
  return gridEventCount > 0;
}

MonomeGridEvent MonomeEventQueue::readGridEvent() {
  if (gridEventCount == 0) return emptyGridEvent;
  gridEventCount--;
  uint8_t index = gridFirstEvent;
  gridFirstEvent = (gridFirstEvent + 1) % MAXEVENTCOUNT;
  return gridEvents[index];
}

void MonomeEventQueue::addArcEvent(uint8_t index, int8_t delta) {
  if (arcEventCount >= MAXEVENTCOUNT) return;
  uint8_t ind = (arcFirstEvent + arcEventCount) % MAXEVENTCOUNT;
  arcEvents[ind].index = index;
  arcEvents[ind].delta = delta;
  arcEventCount++;
}

bool MonomeEventQueue::arcEventAvailable() {
  return arcEventCount > 0;
}

MonomeArcEvent MonomeEventQueue::readArcEvent() {
  if (arcEventCount == 0) return emptyArcEvent;
  arcEventCount--;
  uint8_t index = arcFirstEvent;
  arcFirstEvent = (arcFirstEvent + 1) % MAXEVENTCOUNT;
  return arcEvents[index];
}

void MonomeEventQueue::sendArcDelta(uint8_t index, int8_t delta) {
  /*
    Serial.print("Encoder:");
    Serial.print(index);
    Serial.print(" ");
    Serial.print(delta);
    Serial.println(" ");
    */
  Serial.write((uint8_t)0x50);
  Serial.write((uint8_t)index);
  Serial.write((int8_t)delta);
  /*
    byte buf[3];
    buf[0] = 0x50;
    buf[1] = index;
    buf[2] = delta;
    Serial.write(buf, sizeof(buf));
    */
}

void MonomeEventQueue::sendArcKey(uint8_t index, uint8_t pressed) {
  /*
    Serial.print("key:");
    Serial.print(index);
    Serial.print(" ");
    Serial.println(pressed);
    */
  uint8_t buf[2];
  if (pressed == 1) {
    buf[0] = 0x52;
  } else {
    buf[0] = 0x51;
  }
  buf[1] = index;
  Serial.write(buf, 2);
}

void MonomeEventQueue::sendGridKey(uint8_t x, uint8_t y, uint8_t pressed) {
  uint8_t buf[2];
  if (pressed == 1) {
    buf[0] = 0x21;
  } else {
    buf[0] = 0x20;
  }
  Serial.write((uint8_t)buf[0]);
  Serial.write((uint8_t)x);
  Serial.write((uint8_t)y);
}