void loop() {
  if (client.connected()) {
    isConnected = true;

    report();

    a = client.available();
    if (a > 0) {

      #ifdef DEBUG
      Serial.print("Bytes Available: ");
      Serial.println(a, DEC);
      #endif

      action = client.read();

      #ifdef DEBUG
      Serial.print("Action received: ");
      Serial.println(action, DEC);
      #endif

      // is the action valid?
      if (action <= msg_count) {

        // is there enough data left in the buffer to process this action?
        // if not, stop and fix
        if (msgMinLength[action] <= a) {


          int pin, mode, val, type, speed, address, stop, len, i;
          switch (action) {
            case msg_pinMode:  // pinMode
              pin = client.read();
              mode = client.read();
              #ifdef DEBUG
              Serial.print("PIN received: ");
              Serial.println(pin);
              Serial.print("MODE received: ");
              Serial.println(mode, HEX);
              #endif

              if (mode == 0x00) {
                pinMode(pin, INPUT);
              } else if (mode == 0x02) {
                pinMode(pin, INPUT_PULLUP);
              } else if (mode == 0x03) {
                pinMode(pin, INPUT_PULLDOWN);
              } else if (mode == 0x01) {
                pinMode(pin, OUTPUT);
              } else if (mode == 0x04) {
                pinMode(pin, OUTPUT);
                if (servos[ToServoIndex(pin)].attached()) {
                  servos[ToServoIndex(pin)].detach();
                }
                servos[ToServoIndex(pin)].attach(pin);
              }
              break;

            case msg_digitalWrite:  // digitalWrite
              pin = client.read();
              val = client.read();
              #ifdef DEBUG
              Serial.print("PIN received: ");
              Serial.println(pin, DEC);
              Serial.print("VALUE received: ");
              Serial.println(val, HEX);
              #endif
              digitalWrite(pin, val);
              break;

            case msg_analogWrite:  // analogWrite
              pin = client.read();
              val = client.read();
              #ifdef DEBUG
              Serial.print("PIN received: ");
              Serial.println(pin, DEC);
              Serial.print("VALUE received: ");
              Serial.println(val, HEX);
              #endif
              analogWrite(pin, val);
              break;

            case msg_digitalRead:  // digitalRead
              pin = client.read();
              val = digitalRead(pin);
              #ifdef DEBUG
              Serial.print("PIN received: ");
              Serial.println(pin, DEC);
              Serial.print("VALUE sent: ");
              Serial.println(val, HEX);
              #endif
              server.write(0x03);    // could be (action)
              server.write(pin);
              server.write(val);
              break;

            case msg_analogRead:  // analogRead
              pin = client.read();
              val = analogRead(pin);
              #ifdef DEBUG
              Serial.print("PIN received: ");
              Serial.println(pin, DEC);
              Serial.print("VALUE sent: ");
              Serial.println(val, HEX);
              #endif
              server.write(0x04);    // could be (action)
              server.write(pin);
              server.write(val);
              break;

            case msg_setAlwaysSendBit: // set always send bit
              pin = client.read();
              val = client.read();
              reading[pin] = val;
              break;

            // Serial API
            case msg_serialBegin:  // serial.begin
              type = client.read();
              speed = client.read();
              if (type == 0) {
                Serial.begin(SerialSpeed[speed]);
              } else {
                Serial1.begin(SerialSpeed[speed]);
              }
              break;

            case msg_serialEnd:  // serial.end
              type = client.read();
              if (type == 0) {
                Serial.end();
              } else {
                Serial1.end();
              }
              break;

            case msg_serialPeek:  // serial.peek
              type = client.read();
              if (type == 0) {
                val = Serial.peek();
              } else {
                val = Serial1.peek();
              }
              server.write(0x07);
              server.write(type);
              server.write(val);
              break;

            case msg_serialAvailable:  // serial.available()
              type = client.read();
              if (type == 0) {
                val = Serial.available();
              } else {
                val = Serial1.available();
              }
              server.write(0x07);
              server.write(type);
              server.write(val);
              break;

            case msg_serialWrite:  // serial.write
              type = client.read();
              len = client.read();

              for (i = 0; i < len; i++) {
                if (type == 0) {
                  Serial.write(client.read());
                } else {
                  Serial1.write(client.read());
                }
              }
              break;

            case msg_serialRead: // serial.read
              type = client.read();
              if (type == 0) {
                val = Serial.read();
              } else {
                val = Serial1.read();
              }
              server.write(0x16);
              server.write(type);
              server.write(val);
              break;

            case msg_serialFlush: // serial.flush
              type = client.read();
              if (type == 0) {
                Serial.flush();
              } else {
                Serial1.flush();
              }
              break;

            // SPI API
            case msg_spiBegin:  // SPI.begin
              SPI.begin();
              break;

            case msg_spiEnd:  // SPI.end
              SPI.end();
              break;

            case msg_spiSetBitOrder:  // SPI.setBitOrder
              type = client.read();
              SPI.setBitOrder((type ? MSBFIRST : LSBFIRST));
              break;

            case msg_spiSetClockDivider:  // SPI.setClockDivider
              val = client.read();
              if (val == 0) {
                SPI.setClockDivider(SPI_CLOCK_DIV2);
              } else if (val == 1) {
                SPI.setClockDivider(SPI_CLOCK_DIV4);
              } else if (val == 2) {
                SPI.setClockDivider(SPI_CLOCK_DIV8);
              } else if (val == 3) {
                SPI.setClockDivider(SPI_CLOCK_DIV16);
              } else if (val == 4) {
                SPI.setClockDivider(SPI_CLOCK_DIV32);
              } else if (val == 5) {
                SPI.setClockDivider(SPI_CLOCK_DIV64);
              } else if (val == 6) {
                SPI.setClockDivider(SPI_CLOCK_DIV128);
              } else if (val == 7) {
                SPI.setClockDivider(SPI_CLOCK_DIV256);
              }
              break;

            case msg_spiSetDataMode:  // SPI.setDataMode
              val = client.read();
              if (val == 0) {
                SPI.setDataMode(SPI_MODE0);
              } else if (val == 1) {
                SPI.setDataMode(SPI_MODE1);
              } else if (val == 2) {
                SPI.setDataMode(SPI_MODE2);
              } else if (val == 3) {
                SPI.setDataMode(SPI_MODE3);
              }
              break;

            case msg_spiTransfer:  // SPI.transfer
              val = client.read();
              val = SPI.transfer(val);
              server.write(0x24);
              server.write(val);
              break;

            // Wire API
            case msg_wireBegin:  // Wire.begin
              address = client.read();
              if (address == 0) {
                Wire.begin();
              } else {
                Wire.begin(address);
              }
              break;

            case msg_wireRequestFrom:  // Wire.requestFrom
              address = client.read();
              val = client.read();
              stop = client.read();
              Wire.requestFrom(address, val, stop);
              break;

            case msg_wireBeginTransmission:  // Wire.beginTransmission
              address = client.read();
              Wire.beginTransmission(address);
              break;

            case msg_wireEndTransmission:  // Wire.endTransmission
              stop = client.read();
              val = Wire.endTransmission(stop);
              server.write(0x33);    // could be (action)
              server.write(val);
              break;

            case msg_wireWrite:  // Wire.write
              len = client.read();
              uint8_t wireData[len];

              for (i = 0; i< len; i++) {
                wireData[i] = client.read();
              }
              val = Wire.write(wireData, len);

              server.write(0x34);    // could be (action)
              server.write(val);
              break;

            case msg_wireAvailable:  // Wire.available
              val = Wire.available();
              server.write(0x35);    // could be (action)
              server.write(val);
              break;

            case msg_wireRead:  // Wire.read
              val = Wire.read();
              server.write(0x36);    // could be (action)
              server.write(val);
              break;

            case msg_servoWrite:
              pin = client.read();
              val = client.read();
              #ifdef DEBUG
              Serial.print("PIN: ");
              Serial.println(pin);
              Serial.print("WRITING TO SERVO: ");
              Serial.println(val);
              #endif
              servos[ToServoIndex(pin)].write(val);
              break;

            case msg_servoWriteMicroseconds:
              pin = client.read();
              val = client.read();
              #ifdef DEBUG
              Serial.print("PIN: ");
              Serial.println(pin);
              Serial.print("WRITING 'us' TO SERVO: ");
              Serial.println(val);
              #endif
              servos[ToServoIndex(pin)].writeMicroseconds(val);
              break;

            case msg_servoRead:
              pin = client.read();
              val = servos[ToServoIndex(pin)].read();
              server.write(0x43);    // could be (action)
              server.write(pin);
              server.write(val);
              break;

            case msg_servoDetach:
              pin = client.read();
              servos[ToServoIndex(pin)].detach();
              break;

            default: // noop
              break;

          } // <-- This is the end of the switch
        } // <-- This is the end of if (idx+msgMinLength[] < length)
      } // <-- This is the end of the valid action check
    } // <-- This is the end of the length check
  } else {
    // Upon disconnection, reset the state
    if (isConnected) {
      isConnected = false;
      reset();
    }

    // If no client is yet connected, check for a new connection
    client = server.available();
  }
}
void processInput() {
  int pin, mode, val, type, speed, address, stop, len, k, i;
  int byteCount = bytesRead;

  #if DEBUG
  Serial.println("--------------PROCESSING");
  #endif

  // Only check if buffer[0] is possibly an action
  // when there is no action in progress.
  if (hasAction == false) {
    if (buffer[0] < ACTION_RANGE) {
      action = buffer[0];
      bytesExpecting = bytesToExpectByAction[action] + 1;
      hasAction = true;

      #if DEBUG
      Serial.print("Bytes Read: ");
      Serial.println(bytesRead, DEC);
      Serial.print("Bytes Required: ");
      Serial.println(bytesExpecting, DEC);
      Serial.print("Bytes Remaining: ");
      Serial.println(bytesRead - bytesExpecting, DEC);
      #endif
    }
  }

  if ((bytesRead - bytesExpecting) < 0) {
    hasAction = false;
    bytesExpecting = 0;

    #if DEBUG
    Serial.println("Not Enough Bytes.");
    #endif
    return;
  }

  // When the first byte of buffer is an action and
  // enough bytes are read, begin processing the action.
  if (hasAction && bytesRead >= bytesExpecting) {

    // Copy the expected bytes into the cache and shift
    // the unused bytes to the beginning of the buffer
    for (k = 0; k < byteCount; k++) {
      // Cache the bytes that we're expecting for
      // this action.
      if (k < bytesExpecting) {
        cached[k] = buffer[k];

        // #if DEBUG
        // Serial.print("Cached: ");
        // Serial.println(cached[k], DEC);
        // #endif
      }

      // Shift the unused buffer to the front
      buffer[k] = buffer[k + bytesExpecting];
    }

    byteCount -= bytesExpecting;

    #if DEBUG
    Serial.print("ACTION: ");
    Serial.println(action, DEC);
    #endif

    // Proceed with action processing
    switch (action) {
      case PIN_MODE:  // pinMode
        pin = cached[1];
        mode = cached[2];
        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin);
        Serial.print("MODE: ");
        Serial.println(mode, HEX);
        #endif


        if (pinModeFor[pin] != mode) {

          if (pinModeFor[pin] == 0x04) {
            servos[ToServoIndex(pin)].detach();
          }

          pinModeFor[pin] = mode;

          // The following modes were derived
          // from uses in core-firmware.
          if (mode == 0x00) {
            // INPUT
            pinMode(pin, INPUT_PULLDOWN);
          }
          if (mode == 0x01) {
            // OUTPUT
            pinMode(pin, OUTPUT);
          }
          if (mode == 0x02) {
            // ANALOG INPUT
            pinMode(pin, INPUT);
          }
          if (mode == 0x03) {
            // ANALOG (PWM) OUTPUT
            pinMode(pin, OUTPUT);
          }
          if (mode == 0x04) {
            // SERVO
            pinMode(pin, OUTPUT);
            servos[ToServoIndex(pin)].attach(pin);
          }
        }
        break;

      case DIGITAL_WRITE:  // digitalWrite
        pin = cached[1];
        val = cached[2];
        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin, DEC);
        Serial.print("VALUE: ");
        Serial.println(val, HEX);
        #endif
        digitalWrite(pin, val);
        break;

      case ANALOG_WRITE:  // analogWrite
        pin = cached[1];
        val = cached[2];
        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin, DEC);
        Serial.print("VALUE: ");
        Serial.println(val, HEX);
        #endif
        analogWrite(pin, val);
        break;

      case DIGITAL_READ:  // digitalRead
        pin = cached[1];
        val = digitalRead(pin);
        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin, DEC);
        Serial.print("VALUE: ");
        Serial.println(val, HEX);
        #endif
        send(0x03, pin, val);
        break;

      case ANALOG_READ:  // analogRead
        pin = cached[1];
        val = analogRead(pin);
        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin, DEC);
        Serial.print("VALUE: ");
        Serial.println(val, HEX);
        #endif
        send(0x04, pin, val);
        break;

      case REPORTING: // Add pin to
        pin = cached[1];
        val = cached[2];

        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin, DEC);
        Serial.print("TYPE: ");
        Serial.println(val, DEC);
        #endif

        if (analogReporting[pin] == 0 || reporting[pin] == 0) {
          reporters++;
        }

        if (val == 2) {
          analogReporting[pin] = 1;
        } else {
          reporting[pin] = 1;
        }
        break;

      case SET_SAMPLE_INTERVAL: // set the sampling interval in ms
        sampleInterval = cached[1] + (cached[2] << 7);

        #if DEBUG
        Serial.print("SET_SAMPLE_INTERVAL (2 bytes): ");
        Serial.println(sampleInterval, DEC);
        #endif

        // Lower than ~100ms will likely crash the spark,
        // but
        if (sampleInterval < 20) {
          sampleInterval = 20;
        }
        break;

      // // Serial API
      // case SERIAL_BEGIN:  // serial.begin
      //   type = cached[1];
      //   speed = cached[2];
      //   if (type == 0) {
      //     Serial.begin(SerialSpeed[speed]);
      //   } else {
      //     Serial1.begin(SerialSpeed[speed]);
      //   }
      //   break;

      // case SERIAL_END:  // serial.end
      //   type = cached[1];
      //   if (type == 0) {
      //     Serial.end();
      //   } else {
      //     Serial1.end();
      //   }
      //   break;

      // case SERIAL_PEEK:  // serial.peek
      //   type = cached[1];
      //   if (type == 0) {
      //     val = Serial.peek();
      //   } else {
      //     val = Serial1.peek();
      //   }
      //   send(0x07, type, val);
      //   break;

      // case SERIAL_AVAILABLE:  // serial.available()
      //   type = cached[1];
      //   if (type == 0) {
      //     val = Serial.available();
      //   } else {
      //     val = Serial1.available();
      //   }
      //   send(0x07, type, val);
      //   break;

      // case SERIAL_WRITE:  // serial.write
      //   type = cached[1];
      //   len = cached[2];

      //   for (i = 0; i < len; i++) {
      //     if (type == 0) {
      //       Serial.write(client.read());
      //     } else {
      //       Serial1.write(client.read());
      //     }
      //   }
      //   break;

      // case SERIAL_READ: // serial.read
      //   type = cached[1];
      //   if (type == 0) {
      //     val = Serial.read();
      //   } else {
      //     val = Serial1.read();
      //   }
      //   send(0x16, type, val);
      //   break;

      // case SERIAL_FLUSH: // serial.flush
      //   type = cached[1];
      //   if (type == 0) {
      //     Serial.flush();
      //   } else {
      //     Serial1.flush();
      //   }
      //   break;

      // SPI API
      // case SPI_BEGIN:  // SPI.begin
      //   SPI.begin();
      //   break;

      // case SPI_END:  // SPI.end
      //   SPI.end();
      //   break;

      // case SPI_SET_BIT_ORDER:  // SPI.setBitOrder
      //   type = cached[1];
      //   SPI.setBitOrder((type ? MSBFIRST : LSBFIRST));
      //   break;

      // case SPI_SET_CLOCK:  // SPI.setClockDivider
      //   val = cached[1];
      //   if (val == 0) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV2);
      //   } else if (val == 1) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV4);
      //   } else if (val == 2) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV8);
      //   } else if (val == 3) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV16);
      //   } else if (val == 4) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV32);
      //   } else if (val == 5) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV64);
      //   } else if (val == 6) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV128);
      //   } else if (val == 7) {
      //     SPI.setClockDivider(SPI_CLOCK_DIV256);
      //   }
      //   break;

      // case SPI_SET_DATA_MODE:  // SPI.setDataMode
      //   val = cached[1];
      //   if (val == 0) {
      //     SPI.setDataMode(SPI_MODE0);
      //   } else if (val == 1) {
      //     SPI.setDataMode(SPI_MODE1);
      //   } else if (val == 2) {
      //     SPI.setDataMode(SPI_MODE2);
      //   } else if (val == 3) {
      //     SPI.setDataMode(SPI_MODE3);
      //   }
      //   break;

      // case SPI_TRANSFER:  // SPI.transfer
      //   val = cached[1];
      //   val = SPI.transfer(val);
      //   server.write(0x24);
      //   server.write(val);
      //   break;

      // // Wire API
      // case WIRE_BEGIN:  // Wire.begin
      //   address = cached[1];
      //   if (address == 0) {
      //     Wire.begin();
      //   } else {
      //     Wire.begin(address);
      //   }
      //   break;

      // case WIRE_REQUEST_FROM:  // Wire.requestFrom
      //   address = cached[1];
      //   val = cached[2];
      //   stop = cached[3];
      //   Wire.requestFrom(address, val, stop);
      //   break;

      // case WIRE_BEGIN_TRANSMISSION:  // Wire.beginTransmission
      //   address = cached[1];
      //   Wire.beginTransmission(address);
      //   break;

      // case WIRE_END_TRANSMISSION:  // Wire.endTransmission
      //   stop = cached[1];
      //   val = Wire.endTransmission(stop);
      //   server.write(0x33);    // could be (action)
      //   server.write(val);
      //   break;

      // case WIRE_WRITE:  // Wire.write
      //   len = cached[1];
      //   uint8_t wireData[len];

      //   for (i = 0; i< len; i++) {
      //     wireData[i] = cached[1];
      //   }
      //   val = Wire.write(wireData, len);

      //   server.write(0x34);    // could be (action)
      //   server.write(val);
      //   break;

      // case WIRE_AVAILABLE:  // Wire.available
      //   val = Wire.available();
      //   server.write(0x35);    // could be (action)
      //   server.write(val);
      //   break;

      // case WIRE_READ:  // Wire.read
      //   val = Wire.read();
      //   server.write(0x36);    // could be (action)
      //   server.write(val);
      //   break;

      case SERVO_WRITE:
        pin = cached[1];
        val = cached[2];
        #if DEBUG
        Serial.print("PIN: ");
        Serial.println(pin);
        Serial.print("WRITING TO SERVO: ");
        Serial.println(val);
        #endif
        servos[ToServoIndex(pin)].write(val);
        break;

      default: // noop
        break;
    } // <-- This is the end of the switch

    memset(&cached[0], 0, 4);


    #if DEBUG
    Serial.print("Unprocessed Bytes: ");
    Serial.println(byteCount, DEC);
    #endif


    // Reset hasAction flag (no longer needed for this opertion)
    // action and byte read expectation flags
    hasAction = false;
    bytesExpecting = 0;

    // If there were leftover bytes available,
    // call processInput. This mechanism will
    // continue until the buffer is exhausted.

    bytesRead = byteCount;

    if (byteCount > 2) {
      #if DEBUG
      Serial.println("Calling processInput ");
      #endif

      processInput();
    } else {
      #if DEBUG
      Serial.println("RETURN TO LOOP!");
      #endif
    }
  }
}