#include "FastLED.h"

// matrix size
#define WIDTH  8
#define HEIGHT 8
#define CentreX  (WIDTH >> 1) - 1
#define CentreY (HEIGHT >> 1) -1

// NUM_LEDS = WIDTH * HEIGHT
#define PIXELPIN 23
#define NUM_LEDS      64
#define LAST_VISIBLE_LED 63

// Fire properties
// this Brighness is used as last scaling factor for the "heat".
// It means it does not only dim the fire but also shift it
// to the orange range.
#define BRIGHTNESS 255 
#define FLAMEWIDTH 1 // how big a single flame should be. Bigger value, bigger flame. 0.2 - 2 without fixed borders
#define FLAMECUT 0.5 // roughness of flames in height direction. Big Values - soft flames. 0.2 - 5 without fixed borders
#define FIRST_LAYER_MINIMUM 175 // min "heat" of bottom layer
#define SECOND_LAYER_MINIMUM 25 // min "heat" of second layer
#define FIRESPEED 13 // how fast the perlin noise is parsed
#define FLAMEHEIGHT 72 // the higher the value, the higher the flame. 0-255

// Smoke screen properties
// The smoke screen works best for big fire effects. It effectively cuts of a part of the flames
// from the rest, sometimes; which looks very much fire-like. For small fire effects with low
// LED count in the height, best deactivate it
// speed must be a little different and faster from Firespeed, to be visible. 
// Dimmer should be somewhere in the middle for big fires, and low for small fires.
#define SMOKESPEED 15 // how fast the smoke moves
#define SMOKE_EFFECT 112 // effect of smoke: the higher the value, the dimmer the flames. 0-255
#define CLOUDWIDTH 0.5
#define CLOUDHEIGHT 2
#define STEP_DELAY_MS 10

CRGB leds[NUM_LEDS];

// fire palette roughly like matlab "hot" colormap
// position, r, g, b value.
// max value for "position" is BRIGHTNESS
DEFINE_GRADIENT_PALETTE(hot_gp) {
  0, 0, 0, 0,
  4, 11, 4, 0,
  8, 14, 6, 0,
  12, 17, 7, 0,
  16, 20, 8, 0,
  20, 25, 11, 0,
  24, 31, 15, 0,
  28, 36, 17, 0,
  32, 45, 20, 0,
  36, 55, 24, 0,
  40, 58, 25, 0,
  44, 62, 27, 0,
  48, 66, 29, 0,
  52, 69, 31, 0,
  56, 73, 33, 0,
  60, 77, 35, 0,
  64, 81, 36, 0,
  68, 84, 39, 0,
  72, 88, 40, 0,
  76, 92, 43, 0,
  80, 95, 45, 0,
  85, 99, 46, 0,
  89, 103, 48, 0,
  93, 106, 50, 0,
  97, 110, 53, 0,
  101, 114, 54, 0,
  105, 118, 56, 0,
  109, 121, 58, 0,
  113, 125, 61, 0,
  117, 129, 63, 0,
  121, 132, 64, 0,
  125, 136, 66, 0,
  129, 140, 69, 0,
  133, 143, 71, 0,
  137, 147, 73, 0,
  141, 151, 76, 0,
  145, 155, 81, 0,
  149, 158, 81, 0,
  153, 162, 82, 0,
  157, 166, 83, 0,
  161, 169, 84, 0,
  165, 173, 85, 0,
  170, 177, 89, 0,
  174, 180, 98, 0,
  178, 184, 103, 0,
  182, 188, 108, 0,
  186, 192, 112, 0,
  190, 195, 117, 0,
  194, 199, 120, 0,
  198, 203, 125, 0,
  202, 206, 128, 0,
  206, 210, 131, 0,
  210, 214, 136, 0,
  214, 217, 141, 1,
  218, 223, 144, 1,
  222, 228, 150, 2,
  226, 230, 153, 2,
  230, 236, 158, 2,
  234, 240, 159, 2,
  238, 243, 160, 2,
  242, 247, 161, 2,
  246, 255, 164, 2,
  250, 255, 167, 3,
  255, 255, 169, 3
};
CRGBPalette32 hotPalette = hot_gp;

extern const uint8_t fire_palette[192];
uint8_t getColorFromPalette(uint8_t heatValue, uint8_t color);

uint8_t XY (uint8_t x, uint8_t y);

// parameters and buffer for the noise array
#define NUM_NOISE_LAYERS 2
// two layers of perlin noise make the fire effect
#define FIRENOISE 0
#define SMOKENOISE 1
uint32_t x_noise[NUM_NOISE_LAYERS];
uint32_t y_noise[NUM_NOISE_LAYERS];
uint32_t z_noise[NUM_NOISE_LAYERS];
uint32_t scale_x[NUM_NOISE_LAYERS];
uint32_t scale_y[NUM_NOISE_LAYERS];

uint8_t noise[NUM_NOISE_LAYERS][WIDTH][HEIGHT];
uint8_t noise2[NUM_NOISE_LAYERS][WIDTH][HEIGHT];

uint8_t heat[NUM_LEDS];

void setup() {
  FastLED.addLeds<NEOPIXEL, PIXELPIN>(leds, NUM_LEDS); // Pin für Neopixel
  FastLED.setBrightness(BRIGHTNESS);
  FastLED.setDither(DISABLE_DITHER);
}

void loop() {
  delay(STEP_DELAY_MS);
  Fire2018_2();
  FastLED.show();
}

void Fire2018_2() {
  // some changing values
  // these values are produced by perlin noise to add randomness and smooth transitions
  // millis are divided by 8 to have only relevant values; 
  // then only the last 8 bit are used because inoise16 takes 8 bit values.
  uint16_t ctrl1 = inoise16(11 * millis(), 0, 0);
  uint16_t ctrl2 = inoise16(13 * millis(), 100000, 100000);
  uint16_t  ctrl = ((ctrl1 + ctrl2) >> 1);

  // parameters for the fire heat map
  x_noise[FIRENOISE] = 3 * ctrl * FIRESPEED;
  y_noise[FIRENOISE] = 20 * millis() * FIRESPEED;
  z_noise[FIRENOISE] = 5 * millis() * FIRESPEED;
  scale_x[FIRENOISE] = ctrl1 / FLAMEWIDTH;
  scale_y[FIRENOISE] = ctrl2 / FLAMECUT;

  //calculate the perlin noise data for the fire
  for (uint8_t x = 0; x < WIDTH; x++) {
    uint32_t xoffset = scale_x[FIRENOISE] * (x - CentreX);

    for (uint8_t y = 0; y < HEIGHT; y++) {
      uint32_t yoffset = scale_y[FIRENOISE] * (y - CentreY);
      uint16_t data = ((inoise16(x_noise[FIRENOISE] + xoffset, y_noise[FIRENOISE] + yoffset, z_noise[FIRENOISE])) + 1);
      noise[FIRENOISE][x][y] = data >> 8; // make 8 bit noise from 16 bit data
    }
  }
  
  // parameters for the smoke map
  x_noise[SMOKENOISE] = 3 * ctrl * SMOKESPEED;
  y_noise[SMOKENOISE] = 20 * millis() * SMOKESPEED;
  z_noise[SMOKENOISE] = 5 * millis() * SMOKESPEED;
  scale_x[SMOKENOISE] = ctrl1 / CLOUDWIDTH;
  scale_y[SMOKENOISE] = ctrl2 / CLOUDHEIGHT;

  //calculate the perlin noise data for the smoke
  for (uint8_t x = 0; x < WIDTH; x++) {
    uint32_t xoffset = scale_x[SMOKENOISE] * (x - CentreX);
    for (uint8_t y = 0; y < HEIGHT; y++) {
      uint32_t yoffset = scale_y[SMOKENOISE] * (y - CentreY);
      uint16_t data = ((inoise16(x_noise[SMOKENOISE] + xoffset, y_noise[SMOKENOISE] + yoffset, z_noise[SMOKENOISE])) + 1);
      noise[SMOKENOISE][x][y] = scale8(data>>8, 255-SMOKE_EFFECT);

    }
  }


    //copy everything one line up
  for (uint8_t y = 0; y < HEIGHT - 1; y++) {
      for (uint8_t x = 0; x < WIDTH; x++) {
      heat[XY(x, y)] = heat[XY(x, y + 1)];
      }
  }
  // draw lowest line - seed the fire where it is brightest and hottest
  for (uint8_t x = 0; x < WIDTH; x++) {
      heat[XY(x, HEIGHT-1)] =  noise[FIRENOISE][x][CentreY];
  } 
  // dim the flames based on FIRENOISE noise. 
  // if the FIRENOISE noise is strong, the led goes out fast
  // if the FIRENOISE noise is weak, the led stays on stronger.
  // once the heat is gone, it stays dark.
  for (uint8_t y = 0; y < HEIGHT-1; y++) {
    for (uint8_t x = 0; x < WIDTH; x++) {
      uint8_t dim = noise[FIRENOISE][x][y];
      // high value in FLAMEHEIGHT = less dimming = high flames
      dim = scale8(dim, (255-FLAMEHEIGHT));
      dim = 255 - dim;
      heat[XY(x, y)] = scale8(heat[XY(x, y)] , dim);
    }
  }
  for (uint8_t y = 0; y < HEIGHT; y++) {
    for (uint8_t x = 0; x < WIDTH; x++) {

      // map the colors based on heatmap
      // use the heat map to set the color of the LED from the "hot" palette
      //                               whichpalette    position      brightness     blend or not
      //leds[XY(x, y)] = ColorFromPalette(hotPalette, heat[XY(x, y)], heat[XY(x, y)], LINEARBLEND);
      leds[XY(x, y)] = ColorFromPalette(hotPalette, heat[XY(x, y)], 255, LINEARBLEND);

      // dim the result based on SMOKENOISE noise
      // this is not saved in the heat map - the flame may dim away and come back
      // next iteration.
      leds[XY(x, y)].nscale8(noise[SMOKENOISE][x][y]);

    }
  }
}


/*************************************************************/
/* physical LED layout here */
uint8_t XY (uint8_t x, uint8_t y) {
  // any out of bounds address maps to the first hidden pixel
  // https://macetech.github.io/FastLED-XY-Map-Generator/
  if ( (x >= WIDTH) || (y >= HEIGHT) ) {
    return (LAST_VISIBLE_LED +1);
  }
  const uint8_t XYTable[] = {
     0,   1,   2,   3,   4,   5,   6,   7,
     8,   9,  10,  11,  12,  13,  14,  15,
    16,  17,  18,  19,  20,  21,  22,  23,
    24,  25,  26,  27,  28,  29,  30,  31,
    32,  33,  34,  35,  36,  37,  38,  39,
    40,  41,  42,  43,  44,  45,  46,  47,
    48,  49,  50,  51,  52,  53,  54,  55,
    56,  57,  58,  59,  60,  61,  62,  63
  };


  uint8_t i = (y * WIDTH) + x;
  uint8_t j = XYTable[i];
  return j;
}