/*********
   Blinkinlove / Materix
   Techno-romantic jewelery based on
   Adafruit Feather HUZZAH boards and
   4*8 NeoPixel Matrix FeatherWings
 *********/

#define DEBUG true

#define BUTTON_PIN 2
#define PSEUDO_GND 14 // physically close to button. Set to LOW at setup
#define DEBOUNCE_MILLIS 250
unsigned long last_press = 0;
bool is_active = false;


#include "config.h"

#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
// Default for the featherwing is 16, but for Huzzah it can't work
// 15 is recommended (requires cutting 16 jumper and soldering 15)
#define NEOPIXEL_PIN    15

// How many NeoPixels are attached to the Arduino?
#define NEOPIXEL_COUNT (4*8)

Adafruit_NeoPixel strip(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

#include <PGMWrap.h>

int8_p heart0[8 * 4] PROGMEM = {
  0,   16,   64,     0,   116,    16,   0,   0,
  0,   64,   32,    64,    32,   144,   0,   0,
  0,   0,   128,    48,   128,     0,   0,   0,
  0,   0,     0,   128,     0,     0,   0,   0
};

int8_p heart1[8 * 4] PROGMEM = {
  0,    32,   160,     0,   160,    32,   0,  0,
  0,   192,    64,   128,    64,   192,   0,   0,
  0,     0,   192,    96,   192,     0,   0,   0,
  0,     0,     0,   192,     0,     0,   0,   0
};

void drawImage(int8_p *img, int rscale, int gscale, int bscale) {
  for (int i = 0; i < 8 * 4; i++) {
    int level = img[i];
    strip.setPixelColor(i, strip.Color((level * rscale) / 100, (level * gscale) / 100, (level * bscale) / 100));
  }
  strip.show();
}

#define NUM_FRAMES 9
int8_p *frames[NUM_FRAMES] = {
  heart1, heart1, heart0,
  heart1, heart1, heart0,
  heart0, heart0, heart0
};

#define DURATION_FAST 30
#define DURATION_SLOW 300

int current_frame = 0;
unsigned long last_flip = 0;

#define SCAN_FRAME_DURATION 200
#define SCAN_COMMON_COLOR_YANG strip.Color(16, 0, 64)
#define SCAN_ANIM_COLOR_YANG strip.Color(16, 0, 128)
#define SCAN_COMMON_COLOR_YIN strip.Color(64, 0, 16)
#define SCAN_ANIM_COLOR_YIN strip.Color(128, 0, 16)

#define NUM_SCAN_ANIM_FRAMES 4

int scan_yang_common[] = { 31, -1};
int scan_yang_frame_a[] = { 23, -1};
int scan_yang_frame_b[] = { 15, -1};
int scan_yang_frame_c[] = { 7, -1};
int scan_yang_frame_d[] = { -1};

int *scan_anim_frames_yang[NUM_SCAN_ANIM_FRAMES] = {
  scan_yang_frame_a,
  scan_yang_frame_b,
  scan_yang_frame_c,
  scan_yang_frame_d
};

int scan_yin_common[] = { 31, -1};
int scan_yin_frame_a[] = { 30, -1};
int scan_yin_frame_b[] = { 22, -1};
int scan_yin_frame_c[] = { 23, -1};

int *scan_anim_frames_yin[NUM_SCAN_ANIM_FRAMES] = {
  scan_yin_frame_a,
  scan_yin_frame_b,
  scan_yin_frame_c,
  scan_yin_frame_b
};



// Zzzen vanity logo (scrolls after reset)
int8_p zzzen[24 * 4] PROGMEM = {
  255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 255, 0, 0, 255,
  0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 255, 128, 128, 255, 0, 255, 255, 0, 255,
  0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 255,
  255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 0, 255, 0, 0, 255
};


void scrollZzzen(int offs) {
  for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 8; x++) {
      int xoffs = x + offs, level = 0;
      if (xoffs >= 0 && xoffs < 24) {
        level = zzzen[24 * y + xoffs];
      }
      strip.setPixelColor(8 * y + x, strip.Color(0, 0, level));
    }
  }
  strip.show();
}

#include "ESP8266WiFi.h"



#define SCAN_INTERVAL 6000
unsigned long next_scan = 0;

String my_mac;
String mate_mac;

#define RSSI_NEAR -20
#define RSSI_FAR -100
int rssi = 0;
bool scanning = false;
bool is_yang;

// set rssi to mate's rssi (0 if not found)
void checkScanResult(int numResults) {
  rssi = 0;
  if (numResults == 0) {
    if (DEBUG) {
      Serial.println("no networks found");
    }
  } else {
    if (DEBUG) {
      Serial.print(numResults);
      Serial.println(" networks found");
    }
    for (int i = 0; i < numResults; ++i) {
      if (mate_mac.equals(WiFi.BSSIDstr(i))) {
        rssi = WiFi.RSSI(i);
        break;
      }
    }
  }
  if (DEBUG) {
    Serial.print(mate_mac);
    if (rssi) {
      Serial.print(" RSSI: ");
      Serial.println(rssi);
    } else {
      Serial.println(" not found");
    }
  }
  scanning = false;
}


// Show heart animation frame according to rssi)
// If scanning: indicate that (animation if no rssi, single pixel otherwise)
void animate(int rssi, bool scanning, unsigned long nowmillis) {
  int rscale = is_yang ? 50 : 100, gscale = 0, bscale = is_yang ? 100 : 75;
  if (rssi) {
    int duration = constrain(
                     map(rssi, RSSI_NEAR, RSSI_FAR, DURATION_FAST, DURATION_SLOW),
                     DURATION_FAST, DURATION_SLOW);
    if ((nowmillis - last_flip) > duration || nowmillis < last_flip) {
      drawImage(frames[current_frame], rscale, gscale, bscale);
      current_frame = (current_frame + 1) % NUM_FRAMES;
      last_flip = nowmillis;
    }
  } else {
    drawImage(heart0, rscale, gscale, bscale);
    current_frame = 0;
  }
  if (scanning) {
    // Show scanning indication
    int *frame = is_yang ? scan_yang_common : scan_yin_common;
    for (int i = 0; frame[i] >= 0; i++) {
      strip.setPixelColor(frame[i], is_yang ? SCAN_COMMON_COLOR_YANG : SCAN_COMMON_COLOR_YIN);
    }
    if (!rssi) {
      int frame_index = (nowmillis / SCAN_FRAME_DURATION) % NUM_SCAN_ANIM_FRAMES;
      frame = is_yang ? scan_anim_frames_yang[frame_index] : scan_anim_frames_yin[frame_index];
      for (int i = 0; frame[i] >= 0; i++) {
        strip.setPixelColor(frame[i], is_yang ? SCAN_ANIM_COLOR_YANG : SCAN_ANIM_COLOR_YIN);
      }
    }
  }
  strip.show();
}

void setup()
{
  Serial.begin(115200);
  delay(1000);

  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(PSEUDO_GND, OUTPUT);
  digitalWrite(PSEUDO_GND, LOW);

  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.setBrightness(32); // (max = 255)
  strip.show();            // Turn OFF all pixels ASAP

  for (int offs = -23 ; offs < 25; offs++) {
    scrollZzzen(offs);
    delay(100);
  }

  my_mac = WiFi.softAPmacAddress();
  if (DEBUG) {
    Serial.print("My MAC: ");
    Serial.println(my_mac);
  }
  if (my_mac.equals(YIN)) {
    is_yang = false;
    mate_mac = String(YANG);
  } else {
    is_yang = true;
    mate_mac = String(YIN);
  }
  WiFi.softAP(String("blinkinlove:") + my_mac.substring(12), "whereartthou");
}

void loop()
{
  unsigned long nowmillis = millis();
  if ((nowmillis < last_press || nowmillis > last_press + DEBOUNCE_MILLIS) && digitalRead(BUTTON_PIN) == LOW) {
    last_press = nowmillis;
    is_active = !is_active;
    if (DEBUG) {
      Serial.print("is_active set to ");
      Serial.println(is_active);
    }
    if (!is_active) {
      for (int i = 0; i < NEOPIXEL_COUNT; i++) {
        strip.setPixelColor(i, strip.Color(0, 0, 0));
      }
      strip.show();
    }

  }
  if (is_active) {
    if (nowmillis > next_scan) {
      if (DEBUG) {
        Serial.print("Scanning for ");
        Serial.println(mate_mac);
      }
      scanning = true;
      WiFi.scanNetworksAsync(checkScanResult);
      next_scan = nowmillis + SCAN_INTERVAL;
    }
    animate(rssi, scanning, nowmillis);
  }
  delay(20);
}
