/*
  Matrix.cpp - Max7219 LED Matrix library for Arduino & Wiring
  Copyright (c) 2006 Nicholas Zambetti.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

// TODO: Support segment displays in api?
// TODO: Support varying vendor layouts?

/******************************************************************************
 * Includes
 ******************************************************************************/

extern "C" {
  // AVR LibC Includes
  #include <inttypes.h>
  #include <stdlib.h>

  // Wiring Core Includes
  #undef abs
  #include "WConstants.h"

  // Wiring Core Prototypes
  //void pinMode(uint8_t, uint8_t);
  //void digitalWrite(int, uint8_t);
}

#include "Sprite.h"
#include "Matrix.h"

/******************************************************************************
 * Definitions
 ******************************************************************************/

// Matrix registers
#define REG_NOOP   0x00
#define REG_DIGIT0 0x01
#define REG_DIGIT1 0x02
#define REG_DIGIT2 0x03
#define REG_DIGIT3 0x04
#define REG_DIGIT4 0x05
#define REG_DIGIT5 0x06
#define REG_DIGIT6 0x07
#define REG_DIGIT7 0x08
#define REG_DECODEMODE  0x09
#define REG_INTENSITY   0x0A
#define REG_SCANLIMIT   0x0B
#define REG_SHUTDOWN    0x0C
#define REG_DISPLAYTEST 0x0F

/******************************************************************************
 * Constructors
 ******************************************************************************/

Matrix::Matrix(uint8_t data, uint8_t clock, uint8_t load, uint8_t screens /* = 1 */)
{
  // record pins for sw spi
  _pinData = data;
  _pinClock = clock;
  _pinLoad = load;

  // set ddr for sw spi pins
  pinMode(_pinClock, OUTPUT);
  pinMode(_pinData, OUTPUT);
  pinMode(_pinLoad, OUTPUT);

  // allocate screenbuffers
  _screens = screens;
  _buffer = (uint8_t*)calloc(_screens, 64);
  _maximumX = (_screens * 8);

  // initialize registers
  clear();             // clear display
  setScanLimit(0x07);  // use all rows/digits
  setBrightness(0x0F); // maximum brightness
  setRegister(REG_SHUTDOWN, 0x01);    // normal operation
  setRegister(REG_DECODEMODE, 0x00);  // pixels not integers
  setRegister(REG_DISPLAYTEST, 0x00); // not in test mode
}

/******************************************************************************
 * MAX7219 SPI
 ******************************************************************************/

// sends a single byte by sw spi (no latching)
void Matrix::putByte(uint8_t data)
{
  uint8_t i = 8;
  uint8_t mask;
  while(i > 0) {
    mask = 0x01 << (i - 1);         // get bitmask
    digitalWrite(_pinClock, LOW);   // tick
    if (data & mask){               // choose bit
      digitalWrite(_pinData, HIGH); // set 1
    }else{
      digitalWrite(_pinData, LOW);  // set 0
    }
    digitalWrite(_pinClock, HIGH);  // tock
    --i;                            // move to lesser bit
  }
}

// sets register to a byte value for all screens
void Matrix::setRegister(uint8_t reg, uint8_t data)
{
  digitalWrite(_pinLoad, LOW); // begin
  for(uint8_t i = 0; i < _screens; ++i){
    putByte(reg);  // specify register
    putByte(data); // send data
  }
  digitalWrite(_pinLoad, HIGH);  // latch in data
  digitalWrite(_pinLoad, LOW); // end
}

// syncs row of display with buffer
void Matrix::syncRow(uint8_t row)
{
  if (!_buffer) return;
  
  // uint8_t's can't be negative, so don't test for negative row
  if (row >= 8) return;
  digitalWrite(_pinLoad, LOW); // begin
  for(uint8_t i = 0; i < _screens; ++i){
    putByte(8 - row);                // specify register
    putByte(_buffer[row + (8 * i)]); // send data
  }
  digitalWrite(_pinLoad, HIGH);  // latch in data
  digitalWrite(_pinLoad, LOW); // end
}

/******************************************************************************
 * MAX7219 Configuration
 ******************************************************************************/

// sets how many digits are displayed
void Matrix::setScanLimit(uint8_t value)
{
  setRegister(REG_SCANLIMIT, value & 0x07);
}

// sets brightness of the display
void Matrix::setBrightness(uint8_t value)
{
  setRegister(REG_INTENSITY, value & 0x0F);
}

/******************************************************************************
 * Helper Functions
 ******************************************************************************/

void Matrix::buffer(uint8_t x, uint8_t y, uint8_t value)
{
  if (!_buffer) return;
  
  // uint8_t's can't be negative, so don't test for negative x and y.
  if (x >= _maximumX || y >= 8) return;

  uint8_t offset = x; // record x
  x %= 8;             // make x relative to a single matrix
  offset -= x;        // calculate buffer offset

  // wrap shift relative x for nexus module layout
  if (x == 0){
    x = 8;
  }
  --x;

  // record value in buffer
  if(value){
    _buffer[y + offset] |= 0x01 << x;
  }else{
    _buffer[y + offset] &= ~(0x01 << x);
  }
}

/******************************************************************************
 * User API
 ******************************************************************************/

// buffers and writes to screen
void Matrix::write(uint8_t x, uint8_t y, uint8_t value)
{
  buffer(x, y, value);
  
  // update affected row
  syncRow(y);
}

void Matrix::write(uint8_t x, uint8_t y, Sprite sprite)
{
  for (uint8_t i = 0; i < sprite.height(); i++){
    for (uint8_t j = 0; j < sprite.width(); j++)
      buffer(x + j, y + i, sprite.read(j, i));
      
    syncRow(y + i);
  }
}

// clears screens and buffers
void Matrix::clear(void)
{
  if (!_buffer) return;

  // clear buffer
  for(uint8_t i = 0; i < 8; ++i){
    for(uint8_t j = 0; j < _screens; ++j){
      _buffer[i + (8 * j)] = 0x00;
    }
  }

  // clear registers
  for(uint8_t i = 0; i < 8; ++i){
    syncRow(i);
  }
}