#include "ov7670.h"
#include "pinning.h"
#include "debug.h"

#define REG_GAIN                0x00
#define REG_BLUE                0x01
#define REG_RED                 0x02
#define REG_VREF                0x03
#define REG_COM1                0x04
#define REG_BAVE                0x05
#define REG_GbAVE               0x06
#define REG_AECHH               0x07
#define REG_RAVE                0x08
#define REV_COM2                0x09
#define REG_PID                 0x0a
#define REG_VER                 0x0b
#define REG_COM3                0x0c
#define REG_COM4                0x0d
#define REG_COM5                0x0e
#define REG_COM6                0x0f
#define REG_AECH                0x10
#define REG_CLKRC               0x11
#define REG_COM7                0x12
#define REG_COM8                0x13
#define REG_COM9                0x14
#define REG_COM10               0x15
#define REG_HSTART              0x17
#define REG_HSTOP               0x18
#define REG_VSTRT               0x19
#define REG_VSTOP               0x1a
#define REG_PSHIFT              0x1b
#define REG_MIDH                0x1c
#define REG_MIDL                0x1d
#define REG_MVFP                0x1e
#define REG_LAEC                0x1f
#define REG_ADCCTR0             0x20
#define REG_ADCCTR1             0x21
#define REG_ADCCTR2             0x22
#define REG_ADCCTR3             0x23
#define REG_AEW                 0x24
#define REG_AEB                 0x25
#define REG_VPT                 0x26
#define REG_BBIAS               0x27
#define REG_GbBIAS              0x28
#define REG_EXHCH               0x2a
#define REG_EXHCL               0x2b
#define REG_RBIAS               0x2c
#define REG_ADVFL               0x2d
#define REG_ADVFH               0x2e
#define REG_YAVE                0x2f
#define REG_HSYST               0x30
#define REG_HSYEN               0x31
#define REG_HREF                0x32
#define REG_CHLF                0x33
#define REG_ARBLM               0x34
#define REG_ADC                 0x37
#define REG_ACOM                0x38
#define REG_OFON                0x39
#define REG_TSLB                0x3a
#define REG_COM11               0x3b
#define REG_COM12               0x3c
#define REG_COM13               0x3d
#define REG_COM14               0x3e
#define REG_EDGE                0x3f
#define REG_COM15               0x40
#define REG_COM16               0x41
#define REG_COM17               0x42
#define REG_REG4B               0x4b
#define REG_DNSTH               0x4c
#define REG_MTX1                0x4f
#define REG_MTX2                0x50
#define REG_MTX3                0x51
#define REG_MTX4                0x52
#define REG_MTX5                0x53
#define REG_MTX6                0x54
#define REG_BRIGHT              0x55
#define REG_CONTRAS             0x56
#define REG_CONTRAS_CENTER      0x57
#define REG_MTXS                0x58
#define REG_LCC1                0x62
#define REG_LCC2                0x63
#define REG_LCC3                0x64
#define REG_LCC4                0x65
#define REG_LCC5                0x66
#define REG_MANU                0x67
#define REG_MANV                0x68
#define REG_GFIX                0x69
#define REG_GGAIN               0x6a
#define REG_DBLV                0x6b
#define REG_AWBCTR3             0x6c
#define REG_AWBCTR2             0x6d
#define REG_AWBCTR1             0x6e
#define REG_AWBCTR0             0x6f
#define REG_SCALING_XSC         0x70
#define REG_SCALING_YSC         0x71
#define REG_SCALING_DCWCTR      0x72
#define REG_SCALING_PCLK_DIV    0x73
#define REG_REG74               0x74
#define REG_REG75               0x75
#define REG_REG76               0x76
#define REG_REG77               0x77
#define REG_SLOP                0x7a
#define REG_GAM1                0x7b
#define REG_GAM2                0x7c
#define REG_GAM3                0x7d
#define REG_GAM4                0x7e
#define REG_GAM5                0x7f
#define REG_GAM6                0x80
#define REG_GAM7                0x81
#define REG_GAM8                0x82
#define REG_GAM9                0x83
#define REG_GAM10               0x84
#define REG_GAM11               0x85
#define REG_GAM12               0x86
#define REG_GAM13               0x87
#define REG_GAM14               0x88
#define REG_GAM15               0x89
#define REG_RGB444              0x8c
#define REG_DM_LNL              0x92
#define REG_DM_LNH              0x93
#define REG_LCC6                0x94
#define REG_LCC7                0x95
#define REG_BD50ST              0x9d
#define REG_BD60ST              0x9e
#define REG_HAECC1              0x9f
#define REG_HAECC2              0xa0
#define REG_SCALING_PCLK_DELAY  0xa2
#define REG_NT_CTRL             0xa4
#define REG_AECGMAX             0xa5
#define REG_LPH                 0xa6
#define REG_UPL                 0xa7
#define REG_TPL                 0xa8
#define REG_TPH                 0xa9
#define REG_NALG                0xaa
#define REG_BD60MAX             0xab
#define REG_STR_OPT             0xac
#define REG_STR_R               0xad
#define REG_STR_G               0xae
#define REG_STR_B               0xaf
#define REG_ABLC1               0xb1
#define REG_THL_ST              0xb3
#define REG_THL_DLT             0xb5
#define REG_AD_CHB              0xbe
#define REG_AD_CHR              0xbf
#define REG_AD_CHGb             0xc0
#define REG_AD_CHGr             0xc1
#define REG_SATCR               0xc9

#define I2C_ADDRESS             0x42

uint8_t ImageBuffer[CAMERA_IMAGE_WIDTH * CAMERA_IMAGE_HEIGHT / 8];
static volatile int CurrentLine = 0;
uint8_t LineBuffer[CAMERA_IMAGE_WIDTH + 40];
int LineCount = 0;
static volatile int FrameCount = 0;
volatile int Camera_Captured = 0;
static unsigned int BlackPixels = 0;

#ifdef CONFIG_USE_EXPOSURE_CORRECTION
static volatile int ExposureCorrection = 0;
#endif

volatile unsigned int Camera_BlackPixelCounts[] = {0};
volatile int Camera_FinalFrameCount = -1;

static uint8_t ReadRegister(uint8_t reg)
{
    while(I2C1->SR2 & I2C_SR2_BUSY);
    I2C1->CR1 |= I2C_CR1_START;
    while(~I2C1->SR1 & I2C_SR1_SB);
    I2C1->DR = I2C_ADDRESS;
    while(~I2C1->SR1 & I2C_SR1_ADDR);
    I2C1->SR2;       // Dummy read
    I2C1->DR = reg;  // Write the register number to be read
    while(~I2C1->SR1 & (I2C_SR1_TXE | I2C_SR1_BTF));
    I2C1->CR1 |= I2C_CR1_STOP;

    // Read the register value
    while(I2C1->SR2 & I2C_SR2_BUSY);
    I2C1->CR1 |= I2C_CR1_START;
    while(~I2C1->SR1 & I2C_SR1_SB);
    I2C1->DR = I2C_ADDRESS | 1;
    while(~I2C1->SR1 & I2C_SR1_ADDR);
    I2C1->SR2;       // Dummy read
    I2C1->CR1 |= I2C_CR1_STOP;
    while(~I2C1->SR1 & I2C_SR1_RXNE);
    uint8_t data = I2C1->DR;
    return data;
}

static void WriteRegister(uint8_t reg, uint8_t value)
{
    while(I2C1->SR2 & I2C_SR2_BUSY);
    I2C1->CR1 |= I2C_CR1_START;
    while(~I2C1->SR1 & I2C_SR1_SB);
    I2C1->DR = I2C_ADDRESS;
    while(~I2C1->SR1 & I2C_SR1_ADDR);
    I2C1->SR2;          // Dummy read
    I2C1->DR = reg;     // Write the register number
    while(~I2C1->SR1 & (I2C_SR1_TXE | I2C_SR1_BTF));
    I2C1->DR = value;   // Write the register value
    while(~I2C1->SR1 & (I2C_SR1_TXE | I2C_SR1_BTF));
    I2C1->CR1 |= I2C_CR1_STOP;
}

void Camera_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;

    // Reset pin
    GPIOB->CRH = (GPIOB->CRH
        & ~(0x0f << (4 * PIN_CAMERA_RESET - 32)))
        | (0x01 << (4 * PIN_CAMERA_RESET - 32))     // Output, max. 10 MHz
        ;
    GPIOB->BRR = (1 << PIN_CAMERA_RESET);
    GPIOB->BSRR = (1 << PIN_CAMERA_RESET);

    // Enable MCO for camera main clock line (PLL / 2 -> 24 MHz)
    RCC->CFGR |= RCC_CFGR_MCO;
    GPIOA->CRH = (GPIOA->CRH
        & ~(0x0f << (4 * PIN_CAMERA_MCLK - 32)))
        | (0x0b << (4 * PIN_CAMERA_MCLK - 32))      // Output, max. 50 MHz
        ;

    AFIO->MAPR |= AFIO_MAPR_I2C1_REMAP;

    // I2C interface for camera configuration
    GPIOB->CRH = (GPIOB->CRH
        & ~(0x0f << (4 * PIN_CAMERA_SCL - 32))
        & ~(0x0f << (4 * PIN_CAMERA_SDA - 32)))
        | (0x0e << (4 * PIN_CAMERA_SCL - 32))       // AF OD output, 2 MHz
        | (0x0e << (4 * PIN_CAMERA_SDA - 32))       // AF OD output, 2 MHz
        ;

    I2C1->CR1 = I2C_CR1_SWRST;
    I2C1->CR1 = 0;
    I2C1->CR2 = (24 << I2C_CR2_FREQ_Pos);
    I2C1->CCR = I2C_CCR_FS | I2C_CCR_DUTY | (1 << I2C_CCR_CCR_Pos) | 5;
    I2C1->CR1 = I2C_CR1_PE;

    // Timer setup
    AFIO->MAPR |= AFIO_MAPR_TIM3_REMAP_PARTIALREMAP;
    GPIOB->CRL = (GPIOB->CRL
        & ~(0x0f << (PIN_CAMERA_HSYNC * 4))
        & ~(0x0f << (PIN_CAMERA_PCLK * 4)))
        | (0x04 << (PIN_CAMERA_HSYNC * 4))          // Floating input
        | (0x04 << (PIN_CAMERA_PCLK * 4))           // Floating input
        ;
    GPIOA->CRH = (GPIOA->CRH
        & ~(0x0f << (PIN_CAMERA_VSYNC * 4 - 32)))
        | (0x04 << (PIN_CAMERA_VSYNC * 4 - 32))     // Floating input
        ;

    // TIM1_CH2 is VSYNC
    TIM1->PSC = 0;
    TIM1->ARR = 65535;
    TIM1->CCMR1 = TIM_CCMR1_CC2S_0;
    TIM1->CCER = TIM_CCER_CC2E;
    TIM1->DIER = TIM_DIER_CC2IE;
    NVIC_SetPriority(TIM1_CC_IRQn, 0);
    NVIC_EnableIRQ(TIM1_CC_IRQn);

    // TIM3_CH2 is HSYNC and should trigger an interrupt, while TIM3_CH1 is the
    // pixel clock and should trigger DMA transfers
    TIM3->PSC = 0;
    TIM3->ARR = 1;
    TIM3->CCMR1 = TIM_CCMR1_CC2S_0 | TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1PSC_1;
    TIM3->CCER = TIM_CCER_CC2P | TIM_CCER_CC2E | TIM_CCER_CC1E | TIM_CCER_CC1P;
    TIM3->DIER = TIM_DIER_CC2IE;
    NVIC_SetPriority(TIM3_IRQn, 0);
    NVIC_EnableIRQ(TIM3_IRQn);

    // Fetch GPIOA IDR lower byte
    DMA1_Channel6->CPAR = (uint32_t)&(GPIOA->IDR);

    // Startup delay
    for(volatile int i = 0; i < 1000; i++);

    // Camera configuration
    ReadRegister(REG_PID);

    // Disable timing resets
    WriteRegister(REG_COM6, 0x00);

    // Set clock prescaler to 2
    WriteRegister(REG_CLKRC, 0x4 | 1);

    // Enable scaling
    WriteRegister(REG_COM3, 0x08);

    // Use QCIF output format
    WriteRegister(REG_COM7, 0x08);

    // Blank pixel clock during sync pulses
    WriteRegister(REG_COM10, 0x20);

    // Enable pixel clock scaling
    WriteRegister(REG_COM14, 0x18 | 1);
    WriteRegister(REG_SCALING_PCLK_DIV, 1);

    // Set maximum gain ceiling
    WriteRegister(REG_COM9, 0xca);

    // Disable auto-exposure, enable auto-gain
    WriteRegister(REG_COM8, 0xec);

    // // Disable auto-gain, enable auto-exposure
    // WriteRegister(REG_COM8, 0xe9);

    // Use histogram-based auto-exposure
    WriteRegister(REG_NALG, 0x94);

    // Increase AEC speed:
    // ‘Decrease TPL but not less than UPL [default 0xc1] and increase TPH but
    // not bigger than LPH [default 0xf0]’
    WriteRegister(REG_TPL, 0xc1);
    WriteRegister(REG_TPH, 0xf0);

    // Enable VSYNC interrupts. TIM3 is enabled at the first VSYNC.
    FrameCount = 0;
    Camera_Captured = 0;
    TIM1->CR1 = TIM_CR1_CEN;
}

void TIM1_CC_IRQHandler(void)
{
    // VSYNC
    if(~TIM3->CR1 & TIM_CR1_CEN)
    {
        // Enable HSYNC interrupts and pixel-clock-initiated DMA transfers
        TIM3->CR1 = TIM_CR1_CEN;

        // Return early
        // Dummy read
        TIM1->CCR2;
        TIM1->SR &= ~TIM_SR_CC2IF;
        return;
    }
    

    LineCount = CurrentLine;
    CurrentLine = 0;
    FrameCount++;

#ifdef CONFIG_USE_EXPOSURE_CORRECTION
    // Correct exposure across frames
    ExposureCorrection += 32 * BlackPixels / CAMERA_PIXELS - 16;
#endif

    // Check if the last frame's exposure is reasonable
    if(!Camera_Captured && FrameCount >= CONFIG_MIN_FRAMES
        && (
            (BlackPixels > (int)(CAMERA_PIXELS / 2
                - CAMERA_PIXELS * CONFIG_TOLERANCE)
            && BlackPixels < (int)(CAMERA_PIXELS / 2
                + CAMERA_PIXELS * CONFIG_TOLERANCE))
            || FrameCount >= CONFIG_MAX_FRAMES
        ))
    {
        Camera_Captured = 1;
        Camera_FinalFrameCount = FrameCount;
        // Disable everything
        TIM3->CR1 = 0;
        TIM1->CR1 = 0;
    }

    if(FrameCount < (int)(sizeof(Camera_BlackPixelCounts)
        / sizeof(Camera_BlackPixelCounts[0])))
    {
        Camera_BlackPixelCounts[FrameCount] = BlackPixels;
    }

    // Reset black pixel counter
    BlackPixels = 0;

    // Dummy read
    TIM1->CCR2;
    TIM1->SR &= ~TIM_SR_CC2IF;
}

void TIM3_IRQHandler(void)
{
    // HSYNC
    TIM3->DIER &= ~TIM_DIER_CC1DE;
    TIM3->SR &= ~TIM_SR_CC1IF;

    DMA1_Channel6->CCR = 0;
    DMA1_Channel6->CNDTR = sizeof(LineBuffer);
    DMA1_Channel6->CMAR = (uint32_t)LineBuffer;
    DMA1_Channel6->CCR = DMA_CCR_PL | DMA_CCR_MINC | DMA_CCR_EN;
    TIM3->DIER |= TIM_DIER_CC1DE;

#ifdef CONFIG_USE_2D_DITHERING
    static int8_t y_errors[CAMERA_IMAGE_WIDTH + 2] = {0};

    if(CurrentLine == 0)
    {
        memset(y_errors, 0, sizeof(y_errors));
    }
#endif

    if(!Camera_Captured && (~CurrentLine & 1)
        && (CurrentLine / 2 < CAMERA_IMAGE_HEIGHT))
    {

#ifdef CONFIG_USE_2D_DITHERING
        // Apply errors propagated from the previous line. Since y_errors is
        // overwritten during x error diffusion, this is done now.
        for(int i = 0; i < CAMERA_IMAGE_WIDTH; i++)
        {
            LineBuffer[i + 15] += y_errors[i + 1];
            y_errors[i + 1] = 0;
        }
#endif

        int x_error = 0;
        for(int i = 0; i < CAMERA_IMAGE_WIDTH; i++)
        {
            int pixel = LineBuffer[i + 15] + x_error;
#ifdef CONFIG_USE_EXPOSURE_CORRECTION
            if(ExposureCorrection < 0)
            {
                if(pixel < -ExposureCorrection)
                    pixel = 0;
                else
                    pixel += ExposureCorrection;
            }
            else
            {
                if(pixel > 255 - ExposureCorrection)
                    pixel = 255;
                else
                    pixel += ExposureCorrection;
            }
#endif
            
            int line = CurrentLine / 2;
            int error;
            if(pixel < 127)
            {
                error = pixel;
                ImageBuffer[(line * CAMERA_IMAGE_WIDTH + i) / 8] |=
                    0x80 >> (i % 8);
                BlackPixels++;
            }
            else
            {
                error = pixel - 255;
                ImageBuffer[(line * CAMERA_IMAGE_WIDTH + i) / 8] &=
                    ~(0x80 >> (i % 8));
            }

#ifdef CONFIG_USE_2D_DITHERING
            // Error propagated to the next pixel in the same line
            x_error = error * 7 / 16;

            // Error distributed to the next line's pixels (offset by 1 so
            // bounds checking isn't necessary)
            y_errors[i] += error * 3 / 16;
            y_errors[i + 1] += error * 5 / 16;
            y_errors[i + 2] = error * 1 / 16;
#else
            x_error = error;
#endif
        }
    }

    CurrentLine++;

    // Dummy read
    TIM3->CCR2;
    TIM3->SR &= ~TIM_SR_CC2IF;
}