
#include "mbed.h"
#include "rtos.h"
#include "DisplayBace.h"
#include "ImageReaderSource.h"

#define VIDEO_CVBS             (0)                 /* Analog  Video Signal */
#define VIDEO_CMOS_CAMERA      (1)                 /* Digital Video Signal */

/**** User Selection *********/
/** Camera setting **/
#define VIDEO_INPUT_METHOD     (VIDEO_CMOS_CAMERA) /* Select  VIDEO_CVBS or VIDEO_CMOS_CAMERA                       */
#define USE_VIDEO_CH           (0)                 /* Select  0 or 1            If selecting VIDEO_CMOS_CAMERA, should be 0.)               */
#define VIDEO_PAL              (0)                 /* Select  0(NTSC) or 1(PAL) If selecting VIDEO_CVBS, this parameter is not referenced.) */
/** LCD setting **/
#define LCD_ONOFF              (1)                 /* Select  0(without LCD) or 1(with LCD) */
#define LCD_TYPE               (0)                 /* Select  0(4.3inch) or 1(7.1inch) */
/** JPEG out setting **/
#define JPEG_SEND              (0)                 /* Select  0(JPEG images are not output to PC) or 1(JPEG images are output to PC on USB(CDC) for focusing the camera) */
/** Decode hints **/
#define DECODE_HINTS           (DecodeHints::ONED_HINT | DecodeHints::QR_CODE_HINT | DecodeHints::DATA_MATRIX_HINT | DecodeHints::AZTEC_HINT)
/*****************************/

#if LCD_ONOFF
/** LCD shield config **/
#if (LCD_TYPE == 0)
  #include "LCD_shield_config_4_3inch.h"
#else
  #include "LCD_shield_config_7_1inch.h"
#endif
  #include "AsciiFont.h"
#endif

#ifdef JPEG_SEND
  #include "JPEG_Converter.h"
  #include "DisplayApp.h"
#endif

/** Video and Grapics (GRAPHICS_LAYER_0) parameter **/
/* video input */
#if USE_VIDEO_CH == (0)
  #define VIDEO_INPUT_CH       (DisplayBase::VIDEO_INPUT_CHANNEL_0)
  #define VIDEO_INT_TYPE       (DisplayBase::INT_TYPE_S0_VFIELD)
#else
  #define VIDEO_INPUT_CH       (DisplayBase::VIDEO_INPUT_CHANNEL_1)
  #define VIDEO_INT_TYPE       (DisplayBase::INT_TYPE_S1_VFIELD)
#endif

/* NTSC or PAL */
#if VIDEO_PAL == 0
  #define COL_SYS              (DisplayBase::COL_SYS_NTSC_358)
#else
  #define COL_SYS              (DisplayBase::COL_SYS_PAL_443)
#endif

/* Video input */
#define VIDEO_FORMAT           (DisplayBase::VIDEO_FORMAT_YCBCR422)
#define GRAPHICS_FORMAT        (DisplayBase::GRAPHICS_FORMAT_YCBCR422)
#define WR_RD_WRSWA            (DisplayBase::WR_RD_WRSWA_16BIT)
#define FRAME_BUFFER_BYTE_PER_PIXEL   (2u)

#if VIDEO_INPUT_METHOD == VIDEO_CVBS
  #define WAIT_VFIELD_CNT      (2)
#else
  #define WAIT_VFIELD_CNT      (1)
#endif

/* The size of the video input */
#if ((LCD_ONOFF) && (LCD_TYPE == 0))
  #define VIDEO_PIXEL_HW       LCD_PIXEL_WIDTH
  #define VIDEO_PIXEL_VW       LCD_PIXEL_HEIGHT
#else
  #define VIDEO_PIXEL_HW       (640)  /* VGA */
  #define VIDEO_PIXEL_VW       (480)  /* VGA */
#endif

/*! Frame buffer stride: Frame buffer stride should be set to a multiple of 32 or 128
    in accordance with the frame buffer burst transfer mode. */
/* FRAME BUFFER Parameter GRAPHICS_LAYER_0 */
#define FRAME_BUFFER_STRIDE    (((VIDEO_PIXEL_HW * FRAME_BUFFER_BYTE_PER_PIXEL) + 31u) & ~31u)

static DisplayBase Display;
static Timer decode_timer;

/* 32 bytes aligned */
static uint8_t user_frame_buffer0[FRAME_BUFFER_STRIDE * VIDEO_PIXEL_VW]__attribute((section("NC_BSS"),aligned(32)));

static volatile int32_t vfield_count = 0;
static bool graphics_init_end = false;
static int decode_wait_time = 0;
static void (*p_callback_func)(char * addr, int size);

#if LCD_ONOFF
/****** LCD ******/
static DigitalOut  lcd_pwon(P7_15);
static DigitalOut  lcd_blon(P8_1);
static PwmOut      lcd_cntrst(P8_15);

static void Init_LCD_Display(void) {
    DisplayBase::graphics_error_t error;
    DisplayBase::lcd_config_t lcd_config;
    PinName lvds_pin[8] = {
        /* data pin */
        P5_7, P5_6, P5_5, P5_4, P5_3, P5_2, P5_1, P5_0
    };

    lcd_pwon = 0;
    lcd_blon = 0;
    Thread::wait(100);
    lcd_pwon = 1;
    lcd_blon = 1;

    Display.Graphics_Lvds_Port_Init(lvds_pin, 8);

    /* Graphics initialization process */
    lcd_config = LcdCfgTbl_LCD_shield;
    error = Display.Graphics_init(&lcd_config);
    if (error != DisplayBase::GRAPHICS_OK) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }
    graphics_init_end = true;
}

static void Start_LCD_Display(uint8_t * p_buf) {
    DisplayBase::rect_t rect;

    rect.vs = 0;
    rect.vw = VIDEO_PIXEL_VW;
    rect.hs = 0;
    rect.hw = VIDEO_PIXEL_HW;
    Display.Graphics_Read_Setting(
        DisplayBase::GRAPHICS_LAYER_0,
        (void *)p_buf,
        FRAME_BUFFER_STRIDE,
        GRAPHICS_FORMAT,
        WR_RD_WRSWA,
        &rect
    );
    Display.Graphics_Start(DisplayBase::GRAPHICS_LAYER_0);
}

#define VIDEO_PIXEL_HW_STR              (VIDEO_PIXEL_HW - 64)
#define VIDEO_PIXEL_VW_STR              (VIDEO_PIXEL_VW - 64)
#define FRAME_BUFFER_BYTE_PER_PIXEL_STR (2u)
#define FRAME_BUFFER_STRIDE_STR         (((VIDEO_PIXEL_HW_STR * FRAME_BUFFER_BYTE_PER_PIXEL_STR) + 31u) & ~31u)

static uint8_t user_frame_buffer_string[FRAME_BUFFER_STRIDE_STR * VIDEO_PIXEL_VW_STR]__attribute((section("NC_BSS"),aligned(32)));
static AsciiFont ascii_font(user_frame_buffer_string, VIDEO_PIXEL_HW_STR, VIDEO_PIXEL_VW_STR, FRAME_BUFFER_STRIDE_STR, FRAME_BUFFER_BYTE_PER_PIXEL_STR);
static bool      string_draw;

static void decode_string_init(void) {
    DisplayBase::rect_t rect;

    /* The layer by which the touch panel location is drawn */
    ascii_font.Erase(0x00000000);  /* rrrrGBAR (r:Reserve G:Green B:Blue A:Alpha R:Red */
    rect.vs = 32;
    rect.vw = VIDEO_PIXEL_VW_STR;
    rect.hs = 32;
    rect.hw = VIDEO_PIXEL_HW_STR;
    Display.Graphics_Read_Setting(
        DisplayBase::GRAPHICS_LAYER_1,
        (void *)user_frame_buffer_string,
        FRAME_BUFFER_STRIDE_STR,
        DisplayBase::GRAPHICS_FORMAT_ARGB4444,
        DisplayBase::WR_RD_WRSWA_32_16BIT,
        &rect
    );
    Display.Graphics_Start(DisplayBase::GRAPHICS_LAYER_1);
    string_draw = false;
}

static void decode_string_disp(char ** decode_str) {
    if ((decode_str != NULL) && (*decode_str != NULL)) {
        /* Drow string */
        ascii_font.Erase(0x00000090);  /* rrrrGBAR (r:Reserve G:Green B:Blue A:Alpha R:Red */
        int rest_size = strlen(*decode_str);
        int draw_idx = 0;
        int draw_size;
        int draw_line = 0;

        while (rest_size > 0) {
            draw_size = ascii_font.DrawStr(*decode_str + draw_idx, 6, 5 + (18 * draw_line), 0x0000ffff, 2);
            if (draw_size <= 0) {
                break;
            }
            rest_size -= draw_size;
            draw_idx += draw_size;
            draw_line++;
        }
        string_draw = true;
    } else {
        if (string_draw != false) {
            /* Clear string */
            ascii_font.Erase(0x00000000);  /* rrrrGBAR (r:Reserve G:Green B:Blue A:Alpha R:Red */
            string_draw = false;
        }
    }
}
#endif // LCD_ONOFF

#ifdef JPEG_SEND
/****** Jpeg Send ******/
#define JPEG_ENC_MAX_SIZE      (1024 * 63)
#define JPEG_SIGNAL_CAP_COMP   (1L)

#define JPEGSEND_STB           (0)
#define JPEGSEND_ENC           (1)

static Thread jpegsendTask(osPriorityAboveNormal, 2048);
static DisplayApp  display_app;
static uint8_t JpegBuffer[JPEG_ENC_MAX_SIZE]__attribute((section("NC_BSS"),aligned(8)));  //8 bytes aligned!;
volatile static uint32_t jpegsend_status = JPEGSEND_STB;

static void jpegsendTask_main( void ) {
    JPEG_Converter Jcu;
    size_t jcu_encode_size;

    while (1) {
        Thread::signal_wait(JPEG_SIGNAL_CAP_COMP, 80);
        jpegsend_status = JPEGSEND_ENC;

        /* JPEG Enc Start */
        JPEG_Converter::bitmap_buff_info_t bitmap_buff_info;
        JPEG_Converter::encode_options_t   encode_options;
        bitmap_buff_info.width              = VIDEO_PIXEL_HW;
        bitmap_buff_info.height             = VIDEO_PIXEL_VW;
        bitmap_buff_info.format             = JPEG_Converter::WR_RD_YCbCr422;
        bitmap_buff_info.buffer_address     = (void *)user_frame_buffer0;
        encode_options.encode_buff_size     = sizeof(JpegBuffer);
        encode_options.p_EncodeCallBackFunc = NULL;
        encode_options.input_swapsetting    = JPEG_Converter::WR_RD_WRSWA_16_8BIT;
        jcu_encode_size = 0;
        if (Jcu.encode(&bitmap_buff_info, JpegBuffer, &jcu_encode_size, &encode_options) != JPEG_Converter::JPEG_CONV_OK) {
            jcu_encode_size = 0;
        }

        /* JPEG Data Send */
        if ((jcu_encode_size > 256) && (jcu_encode_size < JPEG_ENC_MAX_SIZE)) {
            display_app.SendJpeg(JpegBuffer, jcu_encode_size);
        }
        jpegsend_status = JPEGSEND_STB;
    }
}

static void jpegsendTask_init( void ) {
    jpegsendTask.start(jpegsendTask_main);
}

static void jpegsend_cap_comp( void ) {
    if (jpegsend_status == JPEGSEND_STB) {
        jpegsendTask.signal_set(JPEG_SIGNAL_CAP_COMP);
    }
}
#endif // JPEG_SEND

/****** Video ******/
static void IntCallbackFunc_Vfield(DisplayBase::int_type_t int_type) {
    /* Interrupt callback function for Vfield interruption */
    if (vfield_count > 0) {
        vfield_count--;
    }
#ifdef JPEG_SEND
    jpegsend_cap_comp();
#endif // JPEG_SEND
}

static void Wait_Vfield(const int32_t wait_count) {
    /* Wait for the specified number of times Vsync occurs */
    vfield_count = wait_count;
    while (vfield_count > 0) {
        Thread::wait(1);
    }
}

static void Init_Video(void) {
    DisplayBase::graphics_error_t error;

    /* Graphics initialization process */
    if (graphics_init_end == false) {
        /* When not initializing LCD, this processing is needed. */
        error = Display.Graphics_init(NULL);
        if (error != DisplayBase::GRAPHICS_OK) {
            printf("Line %d, error %d\n", __LINE__, error);
            mbed_die();
        }
        graphics_init_end = true;
    }

#if VIDEO_INPUT_METHOD == VIDEO_CVBS
    error = Display.Graphics_Video_init( DisplayBase::INPUT_SEL_VDEC, NULL);
    if( error != DisplayBase::GRAPHICS_OK ) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }
#elif VIDEO_INPUT_METHOD == VIDEO_CMOS_CAMERA
    DisplayBase::video_ext_in_config_t ext_in_config;
    PinName cmos_camera_pin[11] = {
        /* data pin */
        P2_7, P2_6, P2_5, P2_4, P2_3, P2_2, P2_1, P2_0,
        /* control pin */
        P10_0,      /* DV0_CLK   */
        P1_0,       /* DV0_Vsync */
        P1_1        /* DV0_Hsync */
    };

    /* MT9V111 camera input config */
    ext_in_config.inp_format     = DisplayBase::VIDEO_EXTIN_FORMAT_BT601; /* BT601 8bit YCbCr format */
    ext_in_config.inp_pxd_edge   = DisplayBase::EDGE_RISING;              /* Clock edge select for capturing data          */
    ext_in_config.inp_vs_edge    = DisplayBase::EDGE_RISING;              /* Clock edge select for capturing Vsync signals */
    ext_in_config.inp_hs_edge    = DisplayBase::EDGE_RISING;              /* Clock edge select for capturing Hsync signals */
    ext_in_config.inp_endian_on  = DisplayBase::OFF;                      /* External input bit endian change on/off       */
    ext_in_config.inp_swap_on    = DisplayBase::OFF;                      /* External input B/R signal swap on/off         */
    ext_in_config.inp_vs_inv     = DisplayBase::SIG_POL_NOT_INVERTED;     /* External input DV_VSYNC inversion control     */
    ext_in_config.inp_hs_inv     = DisplayBase::SIG_POL_INVERTED;         /* External input DV_HSYNC inversion control     */
    ext_in_config.inp_f525_625   = DisplayBase::EXTIN_LINE_525;           /* Number of lines for BT.656 external input */
    ext_in_config.inp_h_pos      = DisplayBase::EXTIN_H_POS_CRYCBY;       /* Y/Cb/Y/Cr data string start timing to Hsync reference */
    ext_in_config.cap_vs_pos     = 6;                                     /* Capture start position from Vsync */
    ext_in_config.cap_hs_pos     = 150;                                   /* Capture start position form Hsync */
#if ((LCD_ONOFF) && (LCD_TYPE == 0))
    /* The same screen ratio as the screen ratio of the LCD. */
    ext_in_config.cap_width      = 640;                                   /* Capture width */
    ext_in_config.cap_height     = 363;                                   /* Capture height Max 468[line]
                                                                             Due to CMOS(MT9V111) output signal timing and VDC5 specification */
#else
    ext_in_config.cap_width      = 640;                                   /* Capture width */
    ext_in_config.cap_height     = 468;                                   /* Capture height Max 468[line]
                                                                             Due to CMOS(MT9V111) output signal timing and VDC5 specification */
#endif
    error = Display.Graphics_Video_init( DisplayBase::INPUT_SEL_EXT, &ext_in_config);
    if( error != DisplayBase::GRAPHICS_OK ) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }

    /* Camera input port setting */
    error = Display.Graphics_Dvinput_Port_Init(cmos_camera_pin, 11);
    if( error != DisplayBase::GRAPHICS_OK ) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }
#endif

    /* Interrupt callback function setting (Field end signal for recording function in scaler 0) */
    error = Display.Graphics_Irq_Handler_Set(VIDEO_INT_TYPE, 0, IntCallbackFunc_Vfield);
    if (error != DisplayBase::GRAPHICS_OK) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }
}

static void Start_Video(uint8_t * p_buf) {
    DisplayBase::graphics_error_t error;

    /* Video capture setting (progressive form fixed) */
    error = Display.Video_Write_Setting(
                VIDEO_INPUT_CH,
                COL_SYS,
                p_buf,
                FRAME_BUFFER_STRIDE,
                VIDEO_FORMAT,
                WR_RD_WRSWA,
                VIDEO_PIXEL_VW,
                VIDEO_PIXEL_HW
            );
    if (error != DisplayBase::GRAPHICS_OK) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }

    /* Video write process start */
    error = Display.Video_Start(VIDEO_INPUT_CH);
    if (error != DisplayBase::GRAPHICS_OK) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }

    /* Video write process stop */
    error = Display.Video_Stop(VIDEO_INPUT_CH);
    if (error != DisplayBase::GRAPHICS_OK) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }

    /* Video write process start */
    error = Display.Video_Start(VIDEO_INPUT_CH);
    if (error != DisplayBase::GRAPHICS_OK) {
        printf("Line %d, error %d\n", __LINE__, error);
        mbed_die();
    }
}

/****** zxing_init ******/
void zxing_init(void (*pfunc)(char * addr, int size)) {

#ifdef JPEG_SEND
    jpegsendTask_init();
#endif // JPEG_SEND
#if LCD_ONOFF
    /* Initialization of LCD */
    Init_LCD_Display();    /* When using LCD, please call before than Init_Video(). */
#endif // LCD_ONOFF
    /* Initialization of Video */
    Init_Video();

    /* Initialization memory */
    memset(user_frame_buffer0, 0, (FRAME_BUFFER_STRIDE * VIDEO_PIXEL_VW));

    /* Start of Video */
    Start_Video(user_frame_buffer0);
    /* Wait for first video drawing */
    Wait_Vfield(WAIT_VFIELD_CNT);
#if LCD_ONOFF
    decode_string_init();
    /* Start of LCD */
    Start_LCD_Display(user_frame_buffer0);
    /* Backlight on */
    Thread::wait(200);
    lcd_cntrst.write(1.0);
#endif // LCD_ONOFF
    p_callback_func = pfunc;
    decode_timer.reset();
    decode_timer.start();
}

/****** zxing_main ******/
int zxing_loop() {
    DisplayBase::graphics_error_t error;
    int decode_result = -1;
    vector<Ref<Result> > results;
    char ** decode_str = NULL;

    Wait_Vfield(WAIT_VFIELD_CNT);
    /* Decode barcode image */
    if (decode_timer.read_ms() >= decode_wait_time) {
        decode_timer.reset();
        DecodeHints hints(DECODE_HINTS);
        hints.setTryHarder(false);
        decode_result = ex_decode(user_frame_buffer0, (FRAME_BUFFER_STRIDE * VIDEO_PIXEL_VW), VIDEO_PIXEL_HW, VIDEO_PIXEL_VW, &results, hints);
        if (decode_result == 0) {
            decode_str = (char **)&(results[0]->getText()->getText());
            int size = strlen(*decode_str);
            if (p_callback_func != NULL) {
                p_callback_func(*decode_str, size);
            }
            decode_wait_time = 500;
        } else {
            decode_wait_time = 10;
        }
#if LCD_ONOFF
        decode_string_disp(decode_str);
#endif // LCD_ONOFF
    }

    return decode_result;
}

