

#include <Arduino.h>

#include "./Painter.h"
#include "./led_layout_array.h"
#include "./dprint.h"
#include "./Keyboard.h"
#include "fl/math/math.h"
#include "fl/math/math.h"
#include "fl/log/log.h"
#include "fl/stl/cstring.h"

namespace {

float LuminanceDecay(float time) {
  typedef InterpData<float, float> Datum;
  static const Datum kData[] = {
    Datum(0,  0),
    Datum(1,  0),
    Datum(10, 0),
    Datum(47, 60),
    Datum(120, 100),
    Datum(230, 160),
    Datum(250, 255),
    Datum(254, 255),
    Datum(255, 64), 
  };
  
  const float key = time * 255.f;
  static const int n = sizeof(kData) / sizeof(kData[0]);    
  float approx_val = Interp(key, kData, n);
  

  static const float k = (1.0f / 255.f);
  const float out = approx_val * k;    
  return out;
}

float CalcLuminance(float time_delta_ms,
                    bool sustain_pedal_on,
                    const Key& key,
                    int key_idx) {
                             
  if (key.mCurrColor.v_ <= 0.0) {
    return 0.0;
  }

  const bool dampened_key = (key_idx < kFirstNoteNoDamp);

  const float decay_factor = CalcDecayFactor(sustain_pedal_on,
                                             key.mOn,
                                             key_idx,
                                             key.mVelocity * (1.f/127.f),  // Normalizing
                                             dampened_key,
                                             time_delta_ms);

  if (key.mOn) {
    //const float brigthness_factor = sin(key.mOrigColor.v_ * PI / 2.0);
    float brigthness_factor = 0.0f;

    if (kUseLedCurtin) {
      brigthness_factor = fl::sqrt(fl::sqrt(key.mOrigColor.v_));
    } else {
      //brigthness_factor = key.mOrigColor.v_ * key.mOrigColor.v_;
      brigthness_factor = key.mOrigColor.v_;
    }
    return LuminanceDecay(decay_factor) * brigthness_factor;
    //return 1.0f;
  } else {
    return decay_factor * key.mOrigColor.v_;
  }
}

float CalcSaturation(float time_delta_ms, const ColorHSV& color, bool key_on) {
  if (color.v_ <= 0.0) {
    return color.s_;
  }
  if (!key_on) {
    return 1.0f;
  }
  static const float kDefaultSaturationTime = 0.05f * 1000.f;
  
  // At time = 0.0 the saturation_factor will be at 0.0 and then transition to 1.0
  float saturation_factor = mapf(time_delta_ms,
                                 0.0f, kDefaultSaturationTime,
                                 0.0f, 1.0f);
  // As time increases the saturation factor will continue
  // to grow past 1.0. We use min to clamp it back to 1.0.
  saturation_factor = fl::min(1.0f, saturation_factor);
  // TODO - make the saturation interpolate between the original
  // color and the unsaturated state.
 return saturation_factor;
}

}  // namespace.


void Painter::Paint(uint32_t now_ms,
                    uint32_t delta_ms,
                    VisState vis_state,
                    KeyboardState* keyboard,
                    LedRopeInterface* light_rope) {
  for (int i = 0; i < KeyboardState::kNumKeys; ++i) {
    Key& key = keyboard->mKeys[i];
    
    const float time_delta_ms = static_cast<float>(now_ms - key.mEventTime);
        
    const float lum = CalcLuminance(time_delta_ms, keyboard->mSustainPedal, key, i);
    const float sat = CalcSaturation(time_delta_ms, key.mCurrColor, key.mOn);

    //if (key.mIdx == 56) {
    //  dprint("lum: "); dprint(lum*255.f); dprint(" sat:"); dprintln(sat*255.f);
    //}
    
    key.mCurrColor.v_ = lum;
    key.mCurrColor.s_ = sat;

    // Removing this line breaks one of the visualizers...
    // TODO: Figure out a cleaner solution.
    light_rope->Set(i, key.mCurrColor.ToRGB());
  }

  LedColumns led_columns = LedLayoutArray();

  switch (vis_state) {
    case Painter::kBlockNote: {
      light_rope->DrawSequentialRepeat(kNumLightsPerNote);
      break;
    }
    case Painter::kColumnNote: {
      light_rope->DrawRepeat(led_columns.array, kNumKeys);
      break;
    }
    case Painter::kVUNote: {
      PaintVuNotes(now_ms, *keyboard, led_columns.array, kNumKeys, light_rope);
      break; 
    }
    case Painter::kVUMidNote: {
      PaintVuMidNotesFade(delta_ms, *keyboard, led_columns.array, kNumKeys, light_rope);
      break;
    }
    
    case Painter::kVegas: { // aka "vegas mode?"
      VegasVisualizer(*keyboard, led_columns.array, kNumKeys, light_rope);
      break;
    }
    
    case Painter::kBrightSurprise: {
      PaintBrightSurprise(*keyboard, led_columns.array, kNumKeys, light_rope);
      break;
    }
    
    case Painter::kVUSpaceInvaders: {
      PaintVuSpaceInvaders(now_ms, *keyboard, led_columns.array, kNumKeys, light_rope);
      break; 
    }

    default:
      dprint("Unknown mode: "); dprint(vis_state); dprint(".\n");
      break;
  }
}

void Painter::PaintVuNotes(uint32_t /*now_ms*/,
                           const KeyboardState& keyboard,
                           const int* led_column_table, int led_column_table_length,
                           LedRopeInterface* led_rope) {


 FL_WARN("\n\n############## VU NOTES ################\n\n");
 
 led_rope->RawBeginDraw();
                     
 for (int i = 0; i < led_column_table_length; ++i) {
    const Key& key = keyboard.mKeys[i];
    
    
    // Map the white keys to the bottom and the black keys to the top.
    bool black_key = false;
    switch (key.mIdx % 12) {
      case 1:
      case 4:
      case 6:
      case 9:
      case 11:
        black_key = true;
        break;
    }
    
    const int pixel_count = led_column_table[i];
    const int draw_pixel_count = fl::ceil(pixel_count * fl::sqrt(key.mCurrColor.v_));
    
    const int black_pixel_count = pixel_count - draw_pixel_count;
    const Color3i& c = *led_rope->GetIterator(i);
    

    const bool reverse_correct = black_key == (key.mIdx % 2);

    if (reverse_correct) {
      for (int j = 0; j < draw_pixel_count; ++j) {
        if (j < draw_pixel_count - 1) {
          led_rope->RawDrawPixel(c);
        } else {
          // Last pixel.
          ColorHSV hsv(random(512) / 512.f, random(512) / 512.f, 1.0);
          led_rope->RawDrawPixel(hsv.ToRGB());
        }
      }
      
      for (int j = 0; j < black_pixel_count; ++j) {
        led_rope->RawDrawPixel(Color3i::Black());
      }       
    } else {
      for (int j = 0; j < black_pixel_count; ++j) {
        led_rope->RawDrawPixel(Color3i::Black());
      } 
      
      for (int j = draw_pixel_count - 1; j >= 0; --j) {
        if (j < draw_pixel_count - 1) {
          led_rope->RawDrawPixel(c);
        } else {
          // Last pixel.
          ColorHSV hsv(random(512) / 512.f, random(512) / 512.f, 1.0);
          led_rope->RawDrawPixel(hsv.ToRGB());
        }
      }
    }
  }
  led_rope->RawCommitDraw();
}

void Painter::PaintVuMidNotesFade(uint32_t /*delta_ms*/,
                                  const KeyboardState& keyboard,
                                  const int* led_column_table, int led_column_table_length,
                                  LedRopeInterface* led_rope) {

  FL_WARN("\n\n############## VU MID NOTES FADE ################\n\n");

  struct DrawPoints {
    int n_black0;
    int n_fade0;
    int n_fill;
    int n_fade1;
    int n_black1;
    float fade_factor;  // 0->1.0

    float SumBrightness() const {
      float out = 0;
      out += n_fill;
      out += (fade_factor * n_fade0);
      out += (fade_factor * n_fade1);
      return out;
    }
  };

  // Generator for the DrawPoints struct above.
  //  n_led: How many led's there are in total.
  //  factor: 0->1, indicates % of led's "on".
  struct F {
    static DrawPoints Generate(int n_led, float factor) {
      DrawPoints out;
      fl::memset(&out, 0, sizeof(out));
      if (n_led == 0 || factor == 0.0f) {
        out.n_black0 = n_led;
        return out;
      }
      const int is_odd = (n_led % 2);
      const int n_half_lights = n_led / 2 + is_odd;
      const float f_half_fill = n_half_lights * factor;
      const int n_half_fill = static_cast<int>(f_half_fill);  // Truncates float.
      
      float fade_pix_perc = f_half_fill - static_cast<float>(n_half_fill);
      int n_fade_pix = fade_pix_perc < 1.0f;
      if (n_half_fill == 0) {
        n_fade_pix = 1;
      }
      int n_half_black = n_half_lights - n_half_fill - n_fade_pix;
      
      int n_fill_pix = 0;
      if (n_half_fill > 0) {
        n_fill_pix = n_half_fill * 2 + (is_odd ? -1 : 0);  
      }
      
      out.n_black0 = n_half_black;
      out.n_fade0 = n_fade_pix;
      out.n_fill = n_fill_pix;
      out.n_fade1 = n_fade_pix;
      if (!n_fill_pix && is_odd) {
        out.n_fade1 = 0;
      }
      out.n_black1 = n_half_black;
      out.fade_factor = fade_pix_perc;
      return out;
    }
 };
                                  
 
 led_rope->RawBeginDraw();
                           
 for (int i = 0; i < led_column_table_length; ++i) {
    const Key& key = keyboard.mKeys[i];

    float active_lights_factor = key.IntensityFactor();

    //if (key.mCurrColor.v_ <= 0.f) {
    //  active_lights_factor = 0.0;
    //}

    const int n_led = led_column_table[i];

    if (active_lights_factor > 0.0f) {
      DrawPoints dp = F::Generate(n_led, active_lights_factor);
      
      ColorHSV hsv = key.mCurrColor;
      hsv.v_ = 1.0;
      Color3i color = hsv.ToRGB();
      // Now figure out optional fade color
      Color3i fade_col;
      ColorHSV c = key.mCurrColor;
      c.v_ = dp.fade_factor;
      fade_col = c.ToRGB();
      
      // Output to graphics.
      led_rope->RawDrawPixels(Color3i::Black(), dp.n_black0);
      led_rope->RawDrawPixels(fade_col, dp.n_fade0);
      led_rope->RawDrawPixels(color, dp.n_fill);
      led_rope->RawDrawPixels(fade_col, dp.n_fade1);
      led_rope->RawDrawPixels(Color3i::Black(), dp.n_black1);

#ifdef DEBUG_PAINTER
      if (active_lights_factor > 0.0) {
        int total_lights_on = dp.SumBrightness();
        //dprint("total_lights_on: "); dprint(total_lights_on);
        //dprint(", total lights written: "); dprintln(total_lights_on + dp.n_black0 + dp.n_black1);

        //float total = (dp.n_fade0 * dp.fade_factor) + (dp.n_fade1 * dp.fade_factor) + static_cast<float>(dp.n_fill);
        #define P(X) dprint(", "#X ": "); dprint(X);

        //dprint("active_lights_factor: "); dprintln(active_lights_factor);

        //P(dp.n_black0); P(dp.n_fade0); P(dp.n_fill); P(dp.n_fade1); P(dp.n_black1); P(dp.fade_factor);
        P(total_lights_on);
        P(active_lights_factor);
        //P(total);
        dprintln("");
      }
#endif
    } else {
      led_rope->RawDrawPixels(Color3i::Black(), n_led);
    }



  }

  
  led_rope->RawCommitDraw();
}

void Painter::VegasVisualizer(const KeyboardState& keyboard,
                              const int* led_column_table, int led_column_table_length,
                              LedRopeInterface* led_rope) {
 
 led_rope->RawBeginDraw();
                           
 uint32_t skipped_lights = 0;
 for (int i = 0; i < led_column_table_length; ++i) {
    const Key& key = keyboard.mKeys[i];
    uint32_t painted_lights = 0;
    
    // % of lights that are active.
    const float active_lights_factor = led_column_table[i] * fl::sqrt(key.mCurrColor.v_);
    const float inactive_lights_factor = 1.0f - active_lights_factor;
    const float taper_point_1 = inactive_lights_factor / 2.0f;
    const float taper_point_2 = taper_point_1 + active_lights_factor; 
    
    const int taper_idx_1 = static_cast<int>(fl::floor(taper_point_1 * led_column_table[i]));
    const int taper_idx_2 = static_cast<int>(fl::floor(taper_point_2 * led_column_table[i]));
    
    const Color3i c = key.mCurrColor.ToRGB();
    
    for (int i = 0; i < taper_idx_1 / 2; ++i) {
      led_rope->RawDrawPixel(Color3i::Black());
      painted_lights++;
    }
    
    int length = taper_idx_2 - taper_idx_1;
    for (int i = 0; i < fl::min(200, length); ++i) {
      led_rope->RawDrawPixel(c);
      painted_lights++;
    }

    length = led_column_table[i] - taper_idx_2;
    for (int i = 0; i < length; ++i) {
      led_rope->RawDrawPixel(Color3i::Black());
      painted_lights++;
    }
    skipped_lights += fl::max(0, static_cast<int32_t>(led_column_table[i]) - static_cast<int32_t>(painted_lights));
  }
  
  for (uint32_t i = 0; i < skipped_lights; ++i) {
    led_rope->RawDrawPixel(Color3i::Black());
  }

  led_rope->RawCommitDraw();
}

void Painter::PaintBrightSurprise(
    const KeyboardState& keyboard,
    const int* led_column_table, int led_column_table_length,
    LedRopeInterface* led_rope) {

  led_rope->RawBeginDraw();
  int total_counted = 0;
  
  float r, g, b;
  r = g = b = 0;
  
  for (int i = 0; i < KeyboardState::kNumKeys; ++i) {
    const Key& key = keyboard.mKeys[i];
    

    if (key.mCurrColor.v_ > 0.0f) {
      const Color3i rgb = key.mCurrColor.ToRGB();
      r += rgb.r_;
      g += rgb.g_;
      b += rgb.b_;
      ++total_counted;
    }
  }
  
  float denom = total_counted ? total_counted : 1;
  r /= denom;
  g /= denom;
  b /= denom;
  

  const Color3i rgb(r, g, b);
  
  for (int i = 0; i < led_column_table_length; ++i) {
    const int n = led_column_table[i];
    for (int i = 0; i < n; ++i) {
      led_rope->RawDrawPixel(rgb);
    }
  }
  led_rope->RawCommitDraw();
}

void Painter::PaintVuSpaceInvaders(uint32_t /*now_ms*/,
                                   const KeyboardState& keyboard,
                                   const int* led_column_table, int led_column_table_length,
                                   LedRopeInterface* led_rope) {
 led_rope->RawBeginDraw();
 
 Color3i black = Color3i::Black();
                           
 for (int i = 0; i < led_column_table_length; ++i) {
    const Key& key = keyboard.mKeys[i];
    
    const int pixel_count = led_column_table[i];
    const int draw_pixel_count = fl::ceil(pixel_count * fl::sqrt(key.mCurrColor.v_));
    
    const int black_pixel_count = pixel_count - draw_pixel_count;
    
    // If i is even
    if (i % 2 == 0) {
     for (int j = 0; j < black_pixel_count; ++j) {
        led_rope->RawDrawPixel(*led_rope->GetIterator(i));
      }
     for (int j = 0; j < draw_pixel_count; ++j) {
        led_rope->RawDrawPixel(black);
     }
    } else {

      for (int j = 0; j < draw_pixel_count; ++j) {
        led_rope->RawDrawPixel(black);
      }
      
      for (int j = 0; j < black_pixel_count; ++j) {
        led_rope->RawDrawPixel(*led_rope->GetIterator(i));
      }
    }
  }
  led_rope->RawCommitDraw();
}
