#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

//#define DEBUG true
#ifdef DEBUG
#include <TinyDebug.h>
#endif
// matrix size
#define WIDTH  2
#define HEIGHT 2
#define CentreX  (WIDTH >> 1) - 1
#define CentreY (HEIGHT >> 1) - 1

// NUM_LEDS = WIDTH * HEIGHT
#define PIXELPIN 1
#define NUM_LEDS      4
#define LAST_VISIBLE_LED 3


#define BRIGHTNESS 250
#define FIRST_LAYER_MINIMUM 140
#define SECOND_LAYER_MINIMUM 20
#define FIRESPEED 4
#define FLAMEHEIGHT 234 // the higher the value, the higher the flame
#define MOVETACT 2 //every how many cycles the flames shall move up the LED sheet
#define STEP_DELAY_MS 100


uint8_t multscale8(uint8_t i, uint8_t scale);
uint8_t nmultscale8(uint8_t i, uint8_t scale);

// Attiny85 compatible perlin implementation
extern const uint8_t myperm[256];
uint8_t myFade(uint8_t t);
uint8_t mygrad(uint8_t hash, uint8_t x);
uint8_t myinoise8(uint8_t x);
uint8_t mygrad3d(uint8_t hash, uint8_t x, uint8_t y, uint8_t z);
uint8_t myinoise83d(uint8_t x, uint8_t y, uint8_t z);

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIXELPIN, NEO_GRB + NEO_KHZ800);

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 1
// two layers of perlin noise make the fire effect
#define FIRENOISE 0
uint8_t x[NUM_NOISE_LAYERS];
uint8_t y[NUM_NOISE_LAYERS];
uint8_t z[NUM_NOISE_LAYERS];
uint8_t scale_x[NUM_NOISE_LAYERS];
uint8_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() {
  #ifdef DEBUG
  Debug.begin();
  #endif

  #if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
    clock_prescale_set(clock_div_1);
  #endif
  delay(200); //security wait to avoid bright white LED's at startup
  strip.begin(); // Initialize the strip
  #ifdef DEBUG
  Debug.print(strip.numPixels());
  Debug.println(" LEDs initialized");
  Debug.print("NUM_LEDS is initialized to ");
  Debug.println(NUM_LEDS);
  #endif
  if (strip.numPixels() == 0){
    strip = Adafruit_NeoPixel(NUM_LEDS, PIXELPIN, NEO_GRB + NEO_KHZ800);
    strip.begin();
  }
  strip.clear(); // Initialize all pixels to 'off'
  strip.setBrightness(BRIGHTNESS); // Set brightness
  strip.show(); 
  delay(200);
}

void loop() {
  delay(STEP_DELAY_MS);
  Fire2018_2();
  strip.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 myinoise8 takes 8 bit values.
  uint8_t ctrl1 = myinoise8(((11 * millis())>>3) & 255);
  uint8_t ctrl2 = myinoise8(((13 * millis())>>3) & 255);
  uint8_t  ctrl = ((ctrl1 + ctrl2) >> 1);
  static uint8_t move = 0 ;

  // parameters for the fire heat map
  x[FIRENOISE] = 3 * ctrl * FIRESPEED;
  y[FIRENOISE] = 20 * millis() * FIRESPEED;
  z[FIRENOISE] = 5 * millis() * FIRESPEED;
  scale_x[FIRENOISE] = ctrl1 >> 1;
  scale_y[FIRENOISE] = ctrl2 >> 1;

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

    for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) {
      uint8_t yoffset = scale_y[FIRENOISE] * (y_count - CentreY);
      uint8_t data = ((myinoise83d(x[FIRENOISE] + xoffset, y[FIRENOISE] + yoffset, z[FIRENOISE])) + 1);
      noise[FIRENOISE][x_count][y_count] = data;
    }
  }
  
  if (move == 0) {
    //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][WIDTH-x][CentreY];
        if (heat[XY(x, HEIGHT-1)] < FIRST_LAYER_MINIMUM) heat[XY(x, HEIGHT-1)] = FIRST_LAYER_MINIMUM;
    }
    move = MOVETACT;
  }
 
  else move -=1;
  
  // 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 for Flameheight = high flames
      dim = multscale8(dim, FLAMEHEIGHT);
      heat[XY(x, y)] = multscale8(heat[XY(x, y)] , dim);
    }
  }
  
  for (uint8_t y = 0; y < HEIGHT; y++) {
    for (uint8_t x = 0; x < WIDTH; x++) {

      // 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.
      uint8_t scaled_heat = heat[XY(x, y)];
      if (y==1 and scaled_heat < SECOND_LAYER_MINIMUM) scaled_heat = SECOND_LAYER_MINIMUM;
      // map the colors based on heatmap
      // use the heat map to set the color of the LED from the "hot" palette
      uint8_t red = getColorFromPalette(scaled_heat, 0);
      uint8_t green = getColorFromPalette(scaled_heat, 1);
      uint8_t blue = getColorFromPalette(scaled_heat, 2);
      strip.setPixelColor(XY(x, y), red, green, blue);

    #ifdef DEBUG
    Debug.print("LED ");
    Debug.print(XY(x, y));
    Debug.print(" of ");
    Debug.print(strip.numPixels());
    Debug.print(" -> red:");
    Debug.print(red);
    Debug.print(" green:");
    Debug.print(green);
    Debug.print(" blue:");
    Debug.println(blue);
    #endif
    }
  }

}



/*******************************************************************************/
/* a personal try on Perlin noise here */

// Attiny85 compatible perlin perm LUT implementation

const uint8_t myperm[256] PROGMEM = {58, 194, 244, 85, 166, 49, 150, 135, 35, 200, 7, 251, 39, 9, 214, 46, 105, 162, 103, 202, 139, 224, 209, 107, 101, 83, 71, 130, 208, 43, 52, 171, 12, 113, 73, 197, 168, 178, 147, 227, 250, 170, 14, 231, 163, 93, 213, 192, 238, 137, 246, 131, 124, 182, 128, 193, 245, 126, 38, 183, 247, 24, 129, 115, 81, 97, 228, 99, 167, 40, 96, 210, 59, 42, 232, 133, 111, 203, 149, 86, 48, 254, 68, 92, 155, 117, 41, 32, 122, 78, 188, 158, 13, 142, 127, 44, 63, 60, 75, 110, 17, 222, 184, 196, 249, 146, 62, 235, 169, 25, 31, 212, 116, 74, 165, 102, 190, 229, 154, 79, 164, 84, 108, 253, 57, 10, 123, 218, 215, 144, 114, 236, 201, 26, 134, 8, 51, 179, 141, 181, 95, 220, 80, 230, 187, 191, 176, 185, 76, 53, 109, 189, 6, 119, 91, 27, 148, 255, 233, 36, 45, 240, 156, 145, 237, 121, 204, 87, 65, 239, 241, 98, 225, 174, 206, 19, 77, 199, 30, 205, 248, 211, 172, 22, 180, 219, 94, 234, 223, 151, 4, 61, 198, 54, 140, 89, 242, 125, 207, 195, 138, 50, 143, 132, 243, 159, 173, 106, 118, 152, 37, 252, 67, 161, 5, 21, 175, 72, 153, 23, 186, 70, 29, 112, 34, 56, 120, 33, 18, 28, 0, 157, 47, 226, 1, 100, 221, 82, 104, 2, 55, 177, 88, 16, 136, 217, 160, 11, 3, 90, 20, 64, 216, 69, 15, 66};

//Attiny85 compatible scale and nscale (negative scale) function
uint8_t multscale8(uint8_t i, uint8_t scale) {
  // makes a brightness value dark for low values of "scale"
  return (i * (uint16_t)scale) >> 8;
}

uint8_t nmultscale8(uint8_t i, uint8_t scale) {
  // makes a brightness value dark for high values of "scale"
  return i - ((i * (uint16_t)scale) >> 8);
}

// Attiny compatible myFade function for Perlin Noise
uint8_t myFade(uint8_t t) {
  uint32_t temp = t;
  return temp * temp * temp * (temp * (temp * 6 - 15) + 10) >> 8;  // Beachte den Rechts-Shift, um das Ergebnis zu normieren
}

// Attiny compatible 1D grad function for Perlin Noise
uint8_t mygrad(uint8_t hash, uint8_t x) {
  uint8_t h = hash & 15;
  return ((h & 8) ? -x : x);
}

// Attiny compatible 8 Bit 1D Perlin Noise function
uint8_t myinoise8(uint8_t x) {
  uint8_t xf = x & 255;
  uint8_t xi = x >> 8;
  uint8_t u = myFade(xf);
  
  uint8_t a = pgm_read_byte(&myperm[xi]);
  uint8_t b = pgm_read_byte(&myperm[xi + 1]);
  
  uint8_t x1 = mygrad(a, xf);
  uint8_t x2 = mygrad(b, xf - 1);  // Korrigiert
  
  return ((1 - u) * x1 + u * x2);
}


// Attiny compatible 3D grad function for Perlin Noise
uint8_t mygrad3d(uint8_t hash, uint8_t x, uint8_t y, uint8_t z) {
  uint8_t h = hash & 15;
  uint8_t u = (h < 8) ? x : y;
  uint8_t v = (h < 4) ? y : ((h == 12 || h == 14) ? x : z);
  int16_t gradValue = ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
  return (gradValue < 0) ? 0 : (gradValue > 255) ? 255 : (uint8_t)gradValue;
}

// Attiny compatible 8 Bit 3D Perlin Noise function
uint8_t myinoise83d(uint8_t x, uint8_t y, uint8_t z) {
  uint8_t xf = x & 255;
  uint8_t yf = y & 255;
  uint8_t zf = z & 255;
  uint8_t xi = x >> 8;
  uint8_t yi = y >> 8;
  uint8_t zi = z >> 8;

  uint8_t u = myFade(xf);
  uint8_t v = myFade(yf);
  uint8_t w = myFade(zf);

  uint8_t xiInc = xi + 1;
  uint8_t yiInc = yi + 1;
  uint8_t ziInc = zi + 1;

  uint8_t aaa = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yi]) + zi]);
  uint8_t aba = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yiInc]) + zi]);
  uint8_t aab = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yi]) + ziInc]);
  uint8_t abb = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yiInc]) + ziInc]);
  uint8_t baa = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yi]) + zi]);
  uint8_t bba = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yiInc]) + zi]);
  uint8_t bab = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yi]) + ziInc]);
  uint8_t bbb = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yiInc]) + ziInc]);

  uint8_t x1 = mygrad3d(aaa, xf, yf, zf);
  uint8_t x2 = mygrad3d(baa, xf - 1, yf, zf);
  uint8_t x3 = mygrad3d(aba, xf, yf - 1, zf);
  uint8_t x4 = mygrad3d(bba, xf - 1, yf - 1, zf);
  uint8_t x5 = mygrad3d(aab, xf, yf, zf - 1);
  uint8_t x6 = mygrad3d(bab, xf - 1, yf, zf - 1);
  uint8_t x7 = mygrad3d(abb, xf, yf - 1, zf - 1);
  uint8_t x8 = mygrad3d(bbb, xf - 1, yf - 1, zf - 1);

  uint8_t y1 = (1 - u) * x1 + u * x2;
  uint8_t y2 = (1 - u) * x3 + u * x4;
  uint8_t y3 = (1 - u) * x5 + u * x6;
  uint8_t y4 = (1 - u) * x7 + u * x8;

  uint8_t z1 = (1 - v) * y1 + v * y2;
  uint8_t z2 = (1 - v) * y3 + v * y4;

  return ((1 - w) * z1 + w * z2);
}



/******************************************************/
/* Fire color palette here */
uint8_t getColorFromPalette(uint8_t heatValue, uint8_t color){
  if (color >= 3) color = 2;
  uint8_t index = heatValue >> 2;
  index = index * 3;
  if (index > 191) index = 191; 
  return   pgm_read_byte(&fire_palette[index + color]);
}

// the list contains 3-tuples r, g, b in a long array
const uint8_t fire_palette[192] PROGMEM = {
11 ,  5 ,  0 , 
14 ,  7 ,  0 ,
17 ,  8 ,  0 ,
20 ,  9 ,  0 ,
25 ,  13 ,  0 ,
31 ,  17 ,  0 ,
36 ,  19 ,  0 ,
45 ,  23 ,  0 ,
55 ,  27 ,  0 ,
58 ,  28 ,  0 ,
62 ,  31 ,  0 ,
66 ,  33 ,  0 ,
69 ,  35 ,  0 ,
73 ,  37 ,  0 ,
77 ,  39 ,  0 ,
81 ,  41 ,  0 ,
84 ,  44 ,  0 ,
88 ,  45 ,  0 ,
92 ,  48 ,  0 ,
95 ,  50 ,  0 ,
99 ,  52 ,  0 ,
103 ,  54 ,  0 ,
106 ,  56 ,  0 ,
110 ,  59 ,  0 ,
114 ,  61 ,  0 ,
118 ,  63 ,  0 ,
121 ,  65 ,  0 ,
125 ,  68 ,  0 ,
129 ,  70 ,  0 ,
132 ,  72 ,  0 ,
136 ,  74 ,  0 ,
140 ,  77 ,  0 ,
143 ,  79 ,  0 ,
147 ,  82 ,  0 ,
151 ,  85 ,  0 ,
155 ,  90 ,  0 ,
158 ,  95 ,  0 ,
162 ,  99 ,  0 ,
166 ,  102 ,  0 ,
169 ,  107 ,  0 ,
173 ,  112 ,  0 ,
177 ,  116 ,  0 ,
180 ,  119 ,  0 ,
184 ,  125 ,  0 ,
188 ,  130 ,  0 ,
192 ,  135 ,  0 ,
195 ,  140 ,  0 ,
199 ,  144 ,  0 ,
203 ,  149 ,  0 ,
206 ,  153 ,  0 ,
210 ,  156 ,  0 ,
214 ,  162 ,  9 ,
217 ,  167 ,  11 ,
221 ,  171 ,  13 ,
225 ,  177 ,  15 ,
229 ,  180 ,  18 ,
232 ,  186 ,  18 ,
236 ,  190 ,  21 ,
240 ,  195 ,  21 ,
243 ,  199 ,  22 ,
247 ,  204 ,  22 ,
251 ,  210 ,  24 ,
255 ,  217 ,  24 ,
};


/*************************************************************/
/* 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[] = {
     1,  2,
     0,  3,
  };  

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