#ifndef MBED_SSD1322_H
#define MBED_SSD1322_H

#include "mbed.h"
#include "GraphicsDisplay.h"

//Display module specs
#define DISPLAY_WIDTH	(256)
#define DISPLAY_HEIGHT	(64)
#define DISPLAY_BUFFER_TYPE uint8_t
#define DISPLAY_BUFFER_TYPE_SIZE (2) // 2 pixel / byte
#define DISPLAY_BUFFER_ELEMENTS 8192 // 256*64 / 2 (because 2 pixels/byte)


/** Mirror mode */
enum mirror_t {X, Y, XY, NONE};

typedef enum {
	IDLE,			// No operation currently ongoing
	CLEARING,		// In the process of clearing the display
	WRITING,		// In the process of sending a display update
	WAIT_CLEAR,		// Going to clear after CS pin timeout
	WAIT_WRITE,		// Going to write after CS pin timeout
	TRANSFERS_DONE,         // Last transfer in progress
	DONE,                   // Done with transmission, waiting for CS pin to become high
} SSD1322_state_t;

/** A common base class for monochrome Display
*/
class SSD1322: public GraphicsDisplay
{

public:


    /** Create a monochrome SSD1322 SPI interface
	 * @param name The name used by the parent class to access the interface
	 */
	SSD1322(int Hz, PinName mosi, PinName miso, PinName sclk, PinName CS, PinName reset, PinName DC,
			const char* name);

    /** Destructor
	 * will free framebuffer
	 */
    virtual ~SSD1322() {};



/////// functions that come for free, but can be overwritten///////////////////////////////////////////////////
/////// ----------------------------------------------------///////////////////////////////////////////////////

    /** Draw a pixel in the specified color.
    * @param x is the horizontal offset to this pixel.
    * @param y is the vertical offset to this pixel.
    * @param color defines the color for the pixel.
    */
    virtual void pixel(int x, int y, unsigned short color);

    /** Set the window, which controls where items are written to the screen.
    * When something hits the window width, it wraps back to the left side
    * and down a row. If the initial write is outside the window, it will
    * be captured into the window when it crosses a boundary.
    * @param x is the left edge in pixels.
    * @param y is the top edge in pixels.
    * @param w is the window width in pixels.
    * @param h is the window height in pixels.
    */
    virtual void window(int x, int y, int w, int h);

    /** Read pixel color at location
    * @param x is the horizontal offset to this pixel.
    * @param y is the vertical offset to this pixel.
    * @returns 16bit color, 0000=Black(pixel set), FFFF=White(pixel clear).
    */
    virtual unsigned short pixelread(int x, int y);

    /** Push a single pixel into the window and increment position.
    * You must first call window() then push pixels in loop.
    * @param color is the pixel color.
    */
    virtual void window_pushpixel(unsigned short color);

    /** Push some pixels of the same color into the window and increment position.
    * You must first call window() then push pixels.
    * @param color is the pixel color.
    * @param count: how many
    */
    virtual void window_pushpixel(unsigned short color, unsigned int count);

    /** Push array of pixel colors into the window and increment position.
    * You must first call window() then push pixels.
    * @param color is the pixel color.
    */
    virtual void window_pushpixelbuf(unsigned short* color, unsigned int lenght);

    /** Framebuffer is used, it needs to be sent to SSD1322 from time to time
    */
    virtual void copy_to_lcd();

    /** set the contrast of the screen
      *
      * @param o contrast 0-63
      * @note may be overrided in case of not standard command
      */
    virtual void set_contrast(int o);

    /** read the contrast level
      *
      */
    int get_contrast(void);

    /** display inverted colors
      *
      * @param o = 0 normal, 1 invert
      */
    void invert(unsigned char o);

    /** clear the entire screen
    * The inherited one sets windomax then fill with background color
    * We override it to speedup
    */
    virtual void cls();

    /** Set the orientation of the screen
    *  x,y: 0,0 is always top left
    *
    * @param o direction to use the screen (0-3)
    * 0 = -90°
    * 1 = default 0°
    * 2 = +90°
    * 3 = +180°
    *
    */
    void set_orientation(int o);

    /** Set ChipSelect high or low
    * @param enable 0/1
    */
    virtual void bus_enable(bool enable);

    /** get display X size in pixels (native, orientation independent)
    * @returns X size in pixels
    */
    int sizeX() {return screensize_X;};

    /** get display Y size in pixels (native, orientation independent)
    * @returns Y size in pixels
    */
    int sizeY() {return screensize_Y;};

////////////////////////////////////////////////////////////////////////////////
    // not implemented yet
//////////////////////////////////////////////////////////////////
  //  virtual unsigned short pixelread(int x, int y){return 0;};
    virtual void window4read(int x, int y, int w, int h){};
    void setscrollarea (int startY, int areasize){};
    void scroll (int lines){};
    void scrollreset(){};
    void FastWindow(bool enable){};

    unsigned int buffsize() { return _PAGES*screensize_X;};
    unsigned short pixelpos(int x, int y);
    void clrbuff(const unsigned char value=0x00);
    unsigned long buffaddr(unsigned int i);
    void fills(const unsigned char value=0xFF);

    void set_row_address(unsigned char start_row=0x00, unsigned char end_row=0x3F);
    void set_column_address(unsigned char start_col=0x00, unsigned char end_col=0x3F);

    virtual void copy_to_lcd(unsigned char from_col, unsigned char to_col,
							 unsigned char from_row, unsigned char to_row);

protected:
    /** Setup some stuff (malloc the buffer, etc)
    */
    void init();

    /** set mirror mode
      * @note may be overridden by specific display init class in case of not standard cmds or inverted wiring
      * @param mode NONE, X, Y, XY
      */
    virtual void mirrorXY(mirror_t mode);

////// functions needed by parent class ///////////////////////////////////////
////// -------------------------------- ///////////////////////////////////////

    /** Send 8bit command to display controller
    *
    * @param cmd: byte to send
    * @note if protocol is SPI16, it will insert NOP cmd before, so if cmd is a 2byte cmd, the second cmd will be broken. Use wr_cmd16 for 2bytes cmds
    */
    void wr_cmd8(unsigned char cmd);

    /** Send 8bit data to display controller
    *
    * @param data: byte to send
    *
    */
    void wr_data8(unsigned char data);

    /** HW reset sequence (without display init commands)
    */
    void hw_reset();

    int contrast;

protected:

    mbed::SPI _spi;
    DigitalOut _CS;
    DigitalOut _RST;
    DigitalOut _DC;
    volatile DISPLAY_BUFFER_TYPE _pixelBuffer[DISPLAY_BUFFER_ELEMENTS]; // one full frame buffer
    DISPLAY_BUFFER_TYPE _trBuffer[DISPLAY_BUFFER_ELEMENTS]; // for sending

    const int screensize_X;
    const int screensize_Y;
    const int _PAGES;
    const int _BPP;
    const int _IC_X_SEGS;
    const int _IC_Y_COMS;
    const int _IC_PAGES;

    // pixel location
    int cur_x;
    int cur_y;
    // window location
    int win_x1;
    int win_x2;
    int win_y1;
    int win_y2;
    int orientation;

    SSD1322_state_t _state;
#ifdef DEVICE_SPI_ASYNCH
    event_callback_t _spiEventCb;
#endif

};

#endif
