#include <TVout.h>
#include <fontALL.h>
#include "MyLogo.h"
#include <avr/pgmspace.h> // For PROGMEM 

TVout TV;

// Screen dimensions
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 106

// VU meter dimensions and positioning
#define VU_WIDTH (SCREEN_WIDTH/2 - 8)   // Half screen width with margins
#define VU_HEIGHT (SCREEN_HEIGHT - 20)  // Increased height for VU meters
#define LEFT_VU_X 6                     // Left margin
#define RIGHT_VU_X (SCREEN_WIDTH/2 + 2) // Right meter position
#define VU_Y 8                          // Top position for VU meters
#define BAR_WIDTH 1
#define BAR_GAP 1

// Label positioning at the bottom
#define LABEL_Y (VU_Y + VU_HEIGHT + 3)  // Labels raised by 1 pixel (was 4, now 3)

// Peak hold settings
#define PEAK_HOLD_TIME 500  // Time in ms to hold the peak
#define PEAK_DECAY_RATE 8    // How fast the peak decays (pixels per second)

// Audio input pins
#define LEFT_INPUT A0
#define RIGHT_INPUT A1
#define SMOOTHING_POT A2    // Potentiometer for smoothing control

// For smoothing the readings
#define MAX_SMOOTHING 10
int leftValues[MAX_SMOOTHING];
int rightValues[MAX_SMOOTHING];
int leftIndex = 0;
int rightIndex = 0;
int currentSmoothing = 5;   // Default smoothing value

// Store previous heights for proper clearing
int prevLeftHeight = 0;
int prevRightHeight = 0;

// Peak hold variables
int leftPeakHeight = 0;
int rightPeakHeight = 0;
int prevLeftPeakHeight = 0;
int prevRightPeakHeight = 0;
unsigned long leftPeakTime = 0;
unsigned long rightPeakTime = 0;

int amplifySignal(int input, float gain) {
  // Apply gain and constrain to valid range
  int amplified = input * gain;
  return constrain(amplified, 0, 1023);
}

void setup() {
  // Initialize TV output
  TV.begin(PAL, SCREEN_WIDTH, SCREEN_HEIGHT);
  
  // Set up font
  TV.select_font(font8x8);

  TV.set_cursor(15, 13);
  TV.print("DIY Arduino");
  TV.set_cursor(4, 40);
  TV.print("Stereo VU Meter");
  TV.draw_rect(3, 38, 121, 13, 1, -1);
  TV.draw_rect(0, 35, 127, 19, 1, -1);
  TV.set_cursor(8, 67);
  TV.print("TVout Library");
  TV.delay(5000);
  TV.clear_screen();
  intro();

  // Initialize smoothing arrays
  for (int i = 0; i < MAX_SMOOTHING; i++) {
    leftValues[i] = 0;
    rightValues[i] = 0;
  }
  
  // Draw static elements
  drawStaticElements();
}

void loop() {
  // Read smoothing potentiometer
  int smoothingRaw = analogRead(SMOOTHING_POT);
  currentSmoothing = map(smoothingRaw, 0, 1023, 1, MAX_SMOOTHING);
  
  // Read and process audio inputs
 // int leftRaw = analogRead(LEFT_INPUT);
//  int rightRaw = analogRead(RIGHT_INPUT);

float sensitivityGain = 5.0; // Adjust this value (1.0 = normal, 2.0 = 2x sensitivity)
int leftRaw = amplifySignal(analogRead(LEFT_INPUT), sensitivityGain);
int rightRaw = amplifySignal(analogRead(RIGHT_INPUT), sensitivityGain);
  
  // Apply smoothing
  leftValues[leftIndex] = leftRaw;
  rightValues[rightIndex] = rightRaw;
  leftIndex = (leftIndex + 1) % currentSmoothing;
  rightIndex = (rightIndex + 1) % currentSmoothing;
  
  long leftSum = 0;
  long rightSum = 0;
  for (int i = 0; i < currentSmoothing; i++) {
    leftSum += leftValues[i];
    rightSum += rightValues[i];
  }
  
  int leftValue = leftSum / currentSmoothing;
  int rightValue = rightSum / currentSmoothing;
  
  // Map the values to the VU meter height
  int leftHeight = map(leftValue, 0, 1023, 0, VU_HEIGHT);
  int rightHeight = map(rightValue, 0, 1023, 0, VU_HEIGHT);
  
  // Update the VU meters
  updateVUMeter(LEFT_VU_X, leftHeight, true);
  updateVUMeter(RIGHT_VU_X, rightHeight, false);
  
  // Update peak values
  updatePeaks(leftHeight, rightHeight);
  
  // Draw peak indicators
  drawPeakIndicators();
  
  // Small delay to control refresh rate
  delay(30);
}

void drawStaticElements() {
  // Clear screen
  TV.clear_screen();
  
  // Draw rounded screen border (1 pixel thick)
  TV.draw_rect(0, 0, SCREEN_WIDTH-1, SCREEN_HEIGHT-1, WHITE);
  
  // Round the corners by drawing pixels at the corners
  TV.set_pixel(0, 1, WHITE);
  TV.set_pixel(1, 0, WHITE);
  TV.set_pixel(SCREEN_WIDTH-2, 0, WHITE);
  TV.set_pixel(SCREEN_WIDTH-1, 1, WHITE);
  TV.set_pixel(0, SCREEN_HEIGHT-2, WHITE);
  TV.set_pixel(1, SCREEN_HEIGHT-1, WHITE);
  TV.set_pixel(SCREEN_WIDTH-2, SCREEN_HEIGHT-1, WHITE);
  TV.set_pixel(SCREEN_WIDTH-1, SCREEN_HEIGHT-2, WHITE);
  
  // Draw VU meter borders
  TV.draw_rect(LEFT_VU_X, VU_Y, VU_WIDTH, VU_HEIGHT, WHITE);
  TV.draw_rect(RIGHT_VU_X, VU_Y, VU_WIDTH, VU_HEIGHT, WHITE);
  
  // Draw continuous bottom line of the frame
  TV.draw_line(0, SCREEN_HEIGHT-1, SCREEN_WIDTH-1, SCREEN_HEIGHT-1, WHITE);

  TV.select_font(font6x8);
  
  // Draw labels at the bottom centered in their sections (raised by 1 pixel)
  TV.print(LEFT_VU_X + (VU_WIDTH - 22)/2, LABEL_Y, "LEFT");
  TV.print(RIGHT_VU_X + (VU_WIDTH - 30)/2, LABEL_Y, "RIGHT");
}

void updateVUMeter(int x, int height, boolean isLeft) {
  int &prevHeight = isLeft ? prevLeftHeight : prevRightHeight;
  
  // If height hasn't changed, do nothing
  if (height == prevHeight) {
    return;
  }
  
  // Calculate inner rectangle bounds (2 pixels narrower on each side, moved 1px right)
  int innerX = x + 3;  // Added 1px to move bars right
  int innerWidth = VU_WIDTH - 4;
  
  // Clear the previous bars if height decreased
  if (height < prevHeight) {
    int clearStartY = VU_Y + VU_HEIGHT - prevHeight;
    int clearEndY = VU_Y + VU_HEIGHT - height;
    
    for (int y = clearStartY; y < clearEndY; y++) {
      // Clear only the inner area of the VU meter
      TV.draw_line(innerX, y, innerX + innerWidth - 1, y, BLACK);
    }
  }
  
  // Draw new bars if height increased
  if (height > prevHeight) {
    int drawStartY = VU_Y + VU_HEIGHT - height;
    int drawEndY = VU_Y + VU_HEIGHT - prevHeight;
    
    for (int y = drawStartY; y < drawEndY; y++) {
      // Only draw bars with the specified pattern
      int barPosition = (VU_Y + VU_HEIGHT - y) % (BAR_WIDTH + BAR_GAP);
      if (barPosition < BAR_WIDTH) {
        TV.draw_line(innerX, y, innerX + innerWidth - 1, y, WHITE);
      }
    }
  }
  // If height decreased but we need to redraw the pattern in the remaining area
  else if (height > 0) {
    // Redraw the bar pattern in the remaining area
    for (int y = VU_Y + VU_HEIGHT - height; y < VU_Y + VU_HEIGHT; y++) {
      int barPosition = (VU_Y + VU_HEIGHT - y) % (BAR_WIDTH + BAR_GAP);
      if (barPosition < BAR_WIDTH) {
        TV.draw_line(innerX, y, innerX + innerWidth - 1, y, WHITE);
      } else {
        TV.draw_line(innerX, y, innerX + innerWidth - 1, y, BLACK);
      }
    }
  }
  
  // Update previous height
  prevHeight = height;
}

void intro() {
  unsigned char w, l, wb;
  int index;
  w = pgm_read_byte(MyLogo);
  l = pgm_read_byte(MyLogo+1);
  if (w & 7)
    wb = w/8 + 1;
  else
    wb = w/8;
  index = wb*(l-1) + 2;
  for (unsigned char i = 1; i < l; i++) {
    TV.bitmap((TV.hres() - w)/2, 0, MyLogo, index, w, i);
    index -= wb;
    TV.delay(50);
  }
  for (unsigned char i = 0; i < (TV.vres() - l); i++) {
    TV.bitmap((TV.hres() - w)/2, i, MyLogo);
    TV.delay(50);
  }
  TV.delay(3000);
  TV.clear_screen();
}

void updatePeaks(int leftHeight, int rightHeight) {
  unsigned long currentTime = millis();
  
  // Update left peak
  if (leftHeight > leftPeakHeight) {
    leftPeakHeight = leftHeight;
    leftPeakTime = currentTime;
  } else if (currentTime - leftPeakTime > PEAK_HOLD_TIME) {
    // Gradually decay the peak after hold time
    int decay = (currentTime - leftPeakTime - PEAK_HOLD_TIME) * PEAK_DECAY_RATE / 1000;
    leftPeakHeight = max(0, leftPeakHeight - decay);
  }
  
  // Update right peak
  if (rightHeight > rightPeakHeight) {
    rightPeakHeight = rightHeight;
    rightPeakTime = currentTime;
  } else if (currentTime - rightPeakTime > PEAK_HOLD_TIME) {
    // Gradually decay the peak after hold time
    int decay = (currentTime - rightPeakTime - PEAK_HOLD_TIME) * PEAK_DECAY_RATE / 1000;
    rightPeakHeight = max(0, rightPeakHeight - decay);
  }
}

void drawPeakIndicators() {
  // Calculate inner rectangle bounds
  int leftInnerX = LEFT_VU_X + 3;
  int rightInnerX = RIGHT_VU_X + 3;
  int innerWidth = VU_WIDTH - 4;
  
  // Ensure peak indicators don't touch the borders (both top and bottom)
  int leftPeakY = VU_Y + VU_HEIGHT - max(1, min(leftPeakHeight, VU_HEIGHT - 1));
  int rightPeakY = VU_Y + VU_HEIGHT - max(1, min(rightPeakHeight, VU_HEIGHT - 1));
  int prevLeftPeakY = VU_Y + VU_HEIGHT - max(1, min(prevLeftPeakHeight, VU_HEIGHT - 1));
  int prevRightPeakY = VU_Y + VU_HEIGHT - max(1, min(prevRightPeakHeight, VU_HEIGHT - 1));
  
  // Clear previous peak indicators if they've moved
  if (prevLeftPeakHeight != leftPeakHeight) {
    TV.draw_line(leftInnerX, prevLeftPeakY, 
                 leftInnerX + innerWidth - 1, prevLeftPeakY, BLACK);
  }
  if (prevRightPeakHeight != rightPeakHeight) {
    TV.draw_line(rightInnerX, prevRightPeakY, 
                 rightInnerX + innerWidth - 1, prevRightPeakY, BLACK);
  }
  
  // Draw new peak indicators (if peak is above 0)
  if (leftPeakHeight > 0) {
    TV.draw_line(leftInnerX, leftPeakY, 
                 leftInnerX + innerWidth - 1, leftPeakY, WHITE);
  }
  if (rightPeakHeight > 0) {
    TV.draw_line(rightInnerX, rightPeakY, 
                 rightInnerX + innerWidth - 1, rightPeakY, WHITE);
  }
  
  // REDRAW THE TOP BORDER to fix any erasure by peak indicators
  TV.draw_line(LEFT_VU_X, VU_Y, LEFT_VU_X + VU_WIDTH, VU_Y, WHITE);
  TV.draw_line(RIGHT_VU_X, VU_Y, RIGHT_VU_X + VU_WIDTH, VU_Y, WHITE);
  
  // Update previous peak heights
  prevLeftPeakHeight = leftPeakHeight;
  prevRightPeakHeight = rightPeakHeight;
}