#ifndef __ov7670_H__
#define __ov7670_H__ __ov7670_H__

#include <stdint.h>

#include <avr/io.h>
#include <util/delay.h>

#include "twi.h"

static const uint8_t OV7670_ADDRESS = 0x42;

void ov7670_init(void);

struct ov7670_register {
  uint8_t reg_num;
  uint8_t value;
};

static inline uint8_t ov7670_write8(uint8_t reg, uint8_t value) {
  uint8_t data[2] = {reg, value};
  return twi_write(OV7670_ADDRESS, data, 2);
}

static inline uint8_t ov7670_vsync(void) { return (PINC & (1 << PC0)) != 0; }

static inline void ov7670_wait_vsync(void) {
  while (ov7670_vsync())
    ; // Wait until low
  while (!ov7670_vsync())
    ; // Wait until high
}

static inline void ov7670_set_rst(void) { PORTD |= (1 << PD3); }

static inline void ov7670_unset_rst(void) { PORTD &= ~(1 << PD3); }

static inline void ov7670_set_wrst(void) { PORTC |= (1 << PC3); }

static inline void ov7670_unset_wrst(void) { PORTC &= ~(1 << PC3); }

static inline void ov7670_toggle_wrst(void) {
  ov7670_set_wrst();
  _delay_us(5);
  ov7670_unset_wrst();
}

static inline void ov7670_set_rrst(void) { PORTC |= (1 << PC2); }

static inline void ov7670_unset_rrst(void) { PORTC &= ~(1 << PC2); }

static inline void ov7670_toggle_rrst(void) {
  ov7670_set_rrst();
  _delay_us(5);
  ov7670_unset_rrst();
}

static inline void ov7670_set_we(void) { PORTC |= (1 << PC1); }

static inline void ov7670_unset_we(void) { PORTC &= ~(1 << PC1); }

static inline void ov7670_set_rck(void) { PORTB |= (1 << PB5); }

static inline void ov7670_unset_rck(void) { PORTB &= ~(1 << PB5); }

static inline void ov7670_toggle_rck(void) {
  ov7670_set_rck();
  _delay_us(5);
  ov7670_unset_rck();
}

static inline uint8_t ov7670_read_bits(void) {
  return (PINB & 0x0F) | (PIND & 0xF0);
}


// Register List borrowed from https://github.com/dasaki/arduvision
/* Registers */
static const uint8_t REG_GAIN = 0x00;     /* Gain lower 8 bits (rest in vref) */
static const uint8_t REG_BLUE = 0x01;     /* blue gain */
static const uint8_t REG_RED = 0x02;      /* red gain */
static const uint8_t REG_VREF = 0x03;     /* Pieces of GAIN, VSTART, VSTOP */
static const uint8_t REG_COM1 = 0x04;     /* Control 1 */
static const uint8_t COM1_CCIR656 = 0x40; /* CCIR656 enable */

static const uint8_t REG_BAVE = 0x05;     /* U/B Average level */
static const uint8_t REG_GbAVE = 0x06;    /* Y/Gb Average level */
static const uint8_t REG_AECHH = 0x07;    /* AEC MS 5 bits */
static const uint8_t REG_RAVE = 0x08;     /* V/R Average level */
static const uint8_t REG_COM2 = 0x09;     /* Control 2 */
static const uint8_t COM2_SSLEEP = 0x10;  /* Soft sleep mode */
static const uint8_t REG_PID = 0x0a;      /* Product ID MSB */
static const uint8_t REG_VER = 0x0b;      /* Product ID LSB */
static const uint8_t REG_COM3 = 0x0c;     /* Control 3 */
static const uint8_t COM3_SWAP = 0x40;    /* Byte swap */
static const uint8_t COM3_SCALEEN = 0x08; /* Enable scaling */
static const uint8_t COM3_DCWEN = 0x04;   /* Enable downsamp/crop/window */
static const uint8_t REG_COM4 = 0x0d;     /* Control 4 */
static const uint8_t REG_COM5 = 0x0e;     /* All "reserved" */
static const uint8_t REG_COM6 = 0x0f;     /* Control 6 */
static const uint8_t REG_AECH = 0x10;     /* More bits of AEC value */
static const uint8_t REG_CLKRC = 0x11;    /* Clocl control */
static const uint8_t CLK_EXT = 0x40;      /* Use external clock directly */
static const uint8_t CLK_SCALE = 0x3f;    /* Mask for internal clock scale */
static const uint8_t REG_COM7 = 0x12; /* Control 7 */ // REG mean address.
static const uint8_t COM7_RESET = 0x80;               /* Register reset */
static const uint8_t COM7_FMT_MASK = 0x38;
static const uint8_t COM7_FMT_VGA = 0x00;
static const uint8_t COM7_FMT_CIF = 0x20;   /* CIF format */
static const uint8_t COM7_FMT_QVGA = 0x10;  /* QVGA format */
static const uint8_t COM7_FMT_QCIF = 0x08;  /* QCIF format */
static const uint8_t COM7_RGB = 0x04;       /* bits 0 and 2 - RGB format */
static const uint8_t COM7_YUV = 0x00;       /* YUV */
static const uint8_t COM7_BAYER = 0x01;     /* Bayer format */
static const uint8_t COM7_PBAYER = 0x05;    /* "Processed bayer" */
static const uint8_t REG_COM8 = 0x13;       /* Control 8 */
static const uint8_t COM8_FASTAEC = 0x80;   /* Enable fast AGC/AEC */
static const uint8_t COM8_AECSTEP = 0x40;   /* Unlimited AEC step size */
static const uint8_t COM8_BFILT = 0x20;     /* Band filter enable */
static const uint8_t COM8_AGC = 0x04;       /* Auto gain enable */
static const uint8_t COM8_AWB = 0x02;       /* White balance enable */
static const uint8_t COM8_AEC = 0x01;       /* Auto exposure enable */
static const uint8_t REG_COM9 = 0x14;       /* Control 9- gain ceiling */
static const uint8_t REG_COM10 = 0x15;      /* Control 10 */
static const uint8_t COM10_HSYNC = 0x40;    /* HSYNC instead of HREF */
static const uint8_t COM10_PCLK_HB = 0x20;  /* Suppress PCLK on horiz blank */
static const uint8_t COM10_HREF_REV = 0x08; /* Reverse HREF */
static const uint8_t COM10_VS_LEAD = 0x04;  /* VSYNC on clock leading edge */
static const uint8_t COM10_VS_NEG = 0x02;   /* VSYNC negative */
static const uint8_t COM10_HS_NEG = 0x01;   /* HSYNC negative */
static const uint8_t REG_HSTART = 0x17;     /* Horiz start high bits */
static const uint8_t REG_HSTOP = 0x18;      /* Horiz stop high bits */
static const uint8_t REG_VSTART = 0x19;     /* Vert start high bits */
static const uint8_t REG_VSTOP = 0x1a;      /* Vert stop high bits */
static const uint8_t REG_PSHFT = 0x1b;      /* Pixel delay after HREF */
static const uint8_t REG_MIDH = 0x1c;       /* Manuf. ID high */
static const uint8_t REG_MIDL = 0x1d;       /* Manuf. ID low */
static const uint8_t REG_MVFP = 0x1e;       /* Mirror / vflip */
static const uint8_t MVFP_MIRROR = 0x20;    /* Mirror image */
static const uint8_t MVFP_FLIP = 0x10;      /* Vertical flip */

static const uint8_t REG_AEW = 0x24;      /* AGC upper limit */
static const uint8_t REG_AEB = 0x25;      /* AGC lower limit */
static const uint8_t REG_VPT = 0x26;      /* AGC/AEC fast mode op region */
static const uint8_t REG_HSYST = 0x30;    /* HSYNC rising edge delay */
static const uint8_t REG_HSYEN = 0x31;    /* HSYNC falling edge delay */
static const uint8_t REG_HREF = 0x32;     /* HREF pieces */
static const uint8_t REG_TSLB = 0x3a;     /* lots of stuff */
static const uint8_t TSLB_YLAST = 0x04;   /* UYVY or VYUY - see com13 */
static const uint8_t REG_COM11 = 0x3b;    /* Control 11 */
static const uint8_t COM11_NIGHT = 0x80;  /* NIght mode enable */
static const uint8_t COM11_NMFR = 0x60;   /* Two bit NM frame rate */
static const uint8_t COM11_HZAUTO = 0x10; /* Auto detect 50/60 Hz */
static const uint8_t COM11_50HZ = 0x08;   /* Manual 50Hz select */
static const uint8_t COM11_EXP = 0x02;
static const uint8_t REG_COM12 = 0x3c;     /* Control 12 */
static const uint8_t COM12_HREF = 0x80;    /* HREF always */
static const uint8_t REG_COM13 = 0x3d;     /* Control 13 */
static const uint8_t COM13_GAMMA = 0x80;   /* Gamma enable */
static const uint8_t COM13_UVSAT = 0x40;   /* UV saturation auto adjustment */
static const uint8_t COM13_UVSWAP = 0x01;  /* V before U - w/TSLB */
static const uint8_t REG_COM14 = 0x3e;     /* Control 14 */
static const uint8_t COM14_DCWEN = 0x10;   /* DCW/PCLK-scale enable */
static const uint8_t REG_EDGE = 0x3f;      /* Edge enhancement factor */
static const uint8_t REG_COM15 = 0x40;     /* Control 15 */
static const uint8_t COM15_R10F0 = 0x00;   /* Data range 10 to F0 */
static const uint8_t COM15_R01FE = 0x80;   /*      01 to FE */
static const uint8_t COM15_R00FF = 0xc0;   /*      00 to FF */
static const uint8_t COM15_RGB565 = 0x10;  /* RGB565 output */
static const uint8_t COM15_RGB555 = 0x30;  /* RGB555 output */
static const uint8_t REG_COM16 = 0x41;     /* Control 16 */
static const uint8_t COM16_AWBGAIN = 0x08; /* AWB gain enable */
static const uint8_t REG_COM17 = 0x42;     /* Control 17 */
static const uint8_t COM17_AECWIN = 0xc0;  /* AEC window - must match COM4 */
static const uint8_t COM17_CBAR = 0x08;    /* DSP Color bar */
/*
 * This matrix defines how the colors are generated, must be
 * tweaked to adjust hue and saturation.
 *
 * Order: v-red, v-green, v-blue, u-red, u-green, u-blue
 * They are nine-bit signed quantities, with the sign bit
 * stored in0x58.Sign for v-red is bit 0, and up from there.
 */
static const uint8_t REG_CMATRIX_BASE = 0x4f;
static const uint8_t CMATRIX_LEN = 6;
static const uint8_t REG_CMATRIX_SIGN = 0x58;
static const uint8_t REG_BRIGHT = 0x55;   /* Brightness */
static const uint8_t REG_CONTRAS = 0x56;  /* Contrast control */
static const uint8_t REG_GFIX = 0x69;     /* Fix gain control */
static const uint8_t REG_REG76 = 0x76;    /* OV's name */
static const uint8_t R76_BLKPCOR = 0x80;  /* Black pixel correction enable */
static const uint8_t R76_WHTPCOR = 0x40;  /* White pixel correction enable */
static const uint8_t REG_RGB444 = 0x8c;   /* RGB 444 control */
static const uint8_t R444_ENABLE = 0x02;  /* Turn on RGB444, overrides 5x5 */
static const uint8_t R444_RGBX = 0x01;    /* Empty nibble at end */
static const uint8_t REG_HAECC1 = 0x9f;   /* Hist AEC/AGC control 1 */
static const uint8_t REG_HAECC2 = 0xa0;   /* Hist AEC/AGC control 2 */
static const uint8_t REG_BD50MAX = 0xa5;  /* 50hz banding step limit */
static const uint8_t REG_HAECC3 = 0xa6;   /* Hist AEC/AGC control 3 */
static const uint8_t REG_HAECC4 = 0xa7;   /* Hist AEC/AGC control 4 */
static const uint8_t REG_HAECC5 = 0xa8;   /* Hist AEC/AGC control 5 */
static const uint8_t REG_HAECC6 = 0xa9;   /* Hist AEC/AGC control 6 */
static const uint8_t REG_HAECC7 = 0xaa;   /* Hist AEC/AGC control 7 */
static const uint8_t REG_BD60MAX = 0xab;  /* 60hz banding step limit */
static const uint8_t MTXS = 0x58;        /* Matrix Coefficient Sign */
static const uint8_t AWBC7 = 0x59;       /* AWB Control 7 */
static const uint8_t AWBC8 = 0x5a;       /* AWB Control 8 */
static const uint8_t AWBC9 = 0x5b;       /* AWB Control 9 */
static const uint8_t AWBC10 = 0x5c;      /* AWB Control 10 */
static const uint8_t AWBC11 = 0x5d;      /* AWB Control 11 */
static const uint8_t AWBC12 = 0x5e;      /* AWB Control 12 */
static const uint8_t REG_GFI = 0x69;     /* Fix gain control */
static const uint8_t GGAIN = 0x6a;       /* G Channel AWB Gain */
static const uint8_t DBLV = 0x6b;
static const uint8_t AWBCTR3 = 0x6c; /* AWB Control 3 */
static const uint8_t AWBCTR2 = 0x6d; /* AWB Control 2 */
static const uint8_t AWBCTR1 = 0x6e; /* AWB Control 1 */
static const uint8_t AWBCTR0 = 0x6f; /* AWB Control 0 */

#endif
