/* This code uses APIs from the Free Image project */

#include <iostream>
#include <stdio.h>
#include <FreeImage.h>

#include "ccl_dd.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

using namespace std;

/**
	FreeImage error handler
	@param fif Format / Plugin responsible for the error 
	@param message Error message
*/
void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) {
	printf("\n*** "); 
	if(fif != FIF_UNKNOWN) {
		printf("%s Format\n", FreeImage_GetFormatFromFIF(fif));
	}
	printf("%s\n", message);
	printf(" ***\n");
}

// Device file descriptor
int ccl_fd;

/* Transmit a row of pixel data to the ccl device */
void write_img_row(BYTE *ip_bits, unsigned int width)
{
  img_row_arg_t ira;
  ira.pix_count = width;
  ira.pix_data = ip_bits;
  if(ioctl(ccl_fd, CCL_WRITE_DATA, &ira))  {
    perror("ioctl(CCL_WRITE_DATA) failed");
    return;
  }
}

/* Read a row of pixel data from the ccl device */
void read_img_row(BYTE *op_bits, unsigned int width)
{
  img_row_arg_t ira;
  ira.pix_count = width;
  ira.pix_data = op_bits;
  if(ioctl(ccl_fd, CCL_READ_DATA, &ira))  {
    perror("ioctl(CCL_READ_DATA) failed");
    return;
  }
}

/* Send commands and status notifications to the ccl device*/
void write_register(unsigned int address, unsigned int val)
{
  reg_arg_t ra;
  ra.address = address;
  ra.reg_data = val;
  if(ioctl(ccl_fd, CCL_WRITE_REG, &ra))  {
    perror("ioctl(CCL_WRITE_REG) failed");
    return;
  }
}

/* Poll status registers to control program flow */
int poll_register(unsigned int address)
{
  reg_arg_t ra;
  ra.address = address;
  if(ioctl(ccl_fd, CCL_POLL_REG, &ra))  {
    perror("ioctl(CCL_POLL_REG) failed");
    return -1;
  }
  return ra.reg_data;
}

int main()
{
  static const char filename[] = "/dev/ccl";

  // Connect to ccl device
  if ( (ccl_fd = open(filename, O_RDWR)) == -1)  {
    fprintf(stderr, "could not open %s\n", filename);
    return -1;
  }

  // Initialize the FreeImage library
  FreeImage_Initialise(TRUE);

  // Print FreeImage copyright message
  const char *copyright = FreeImage_GetCopyrightMessage();
  printf("%s\n", copyright);

  // Initialize our own FreeImage error handler
  FreeImage_SetOutputMessage(FreeImageErrorHandler);

 
  // Read image from file
  FIBITMAP *img = FreeImage_Load(FIF_PNG, "test4.png", BMP_DEFAULT);
  
  // Initialize variables for intermediate data and results
  FIBITMAP *img_8b = NULL;
  FIBITMAP *res1_img = NULL;
  FIBITMAP *res2_img = NULL;
  unsigned int width, height, bpp, wib, pitch;

  if (img)  {

    // Collects and prints image information
    width  = FreeImage_GetWidth(img);
    height  = FreeImage_GetHeight(img);
    bpp  = FreeImage_GetBPP(img);
    wib  = FreeImage_GetLine(img);
    pitch  = FreeImage_GetPitch(img);
    printf("Loaded Image. Width: %d Height: %d BPP: %d\n", width, height, bpp);
    printf("Width-Bytes: %d Stride: %d\n", wib, pitch);
    
    // Converts image to 8bit greyscale images if the input is in a different format
    if (bpp != 8)  {
      img_8b = FreeImage_ConvertTo8Bits(img);
    }
    else  {
      img_8b = FreeImage_Clone(img);
    }
    if(!img_8b)  {
      printf("Invalid pointer for 8 bit img\n");
    }

    // Allocate memory for result images
    res1_img = FreeImage_Allocate(width, height, 8);
    res2_img = FreeImage_Allocate(width, height, 8);
    
    // Reset img_transferred to 0
    write_register(182, 0);

    // Set mode 1
    write_register(180, 1);

    // Send image to ccl device for first stage of processing
    int write_idx = height-1, read_idx = height-1;
    while(read_idx >= 0)  {
      // Check write ready
      if(poll_register(184) && write_idx >= 0)  {
        BYTE *ip_bits = FreeImage_GetScanLine(img_8b, write_idx);
        write_img_row(ip_bits, width);
        write_idx--;
      }
      // Check read ready
      else if(poll_register(185) && read_idx >= 0)  {
        BYTE *op_bits = FreeImage_GetScanLine(res1_img, read_idx);
        read_img_row(op_bits, width);
        read_idx--;
      }
      if (write_idx < 0)  {
        // Set img_transferred
        write_register(182, 1);
      }
    }

    // Reset mode 1
    write_register(180, 0);

    // Print max number of computed lables
    printf("Max Labels: %d\n", poll_register(201));
    
    // Wait till LUT values are resolved
    while(!poll_register(186)) {
      usleep(10000);
    }

    // Set img_transferred
    write_register(182, 0);

    // Set mode 2
    write_register(181, 1);

    // Send image for second stage of processing
    write_idx = height-1;
    read_idx = height-1;
    while(read_idx >= 0)  {
      // Check write ready
      if(poll_register(184) && write_idx >= 0)  {
        BYTE *ip_bits = FreeImage_GetScanLine(res1_img, write_idx);
        write_img_row(ip_bits, width);
        write_idx--;
      }
      // Check read ready
      else if(poll_register(185) && read_idx >= 0)  {
        BYTE *op_bits = FreeImage_GetScanLine(res2_img, read_idx);
        read_img_row(op_bits, width);
        read_idx--;
      }
      if (write_idx < 0)  {
        // Set img_transferred
        write_register(182, 1);
      }
    }

    // Reset mode 2
    write_register(181, 0);
    
    // Save pass-1 result image
    if (FreeImage_Save(FIF_PNG, res1_img, "result1.png", 0))  {
      printf("Saved Image\n");
    }
    else  {
      printf("Save Failed\n");
    }

    // Save pass-2 result image
    if (FreeImage_Save(FIF_PNG, res2_img, "result2.png", 0))  {
      printf("Saved Image\n");
    }
    else  {
      printf("Save Failed\n");
    }

    // Set background pixels to 255 to improve visibility of labeled regions
    for (int i = height-1; i >= 0; i--)  {
      BYTE *bits = FreeImage_GetScanLine(res2_img, i);
      for (int j = 0; j < width; j++)  {
        if(bits[j] == 0)  {
          bits[j] = 255;
        }
      }
    }

    // Save final result image
    if (FreeImage_Save(FIF_PNG, res2_img, "result.png", 0))  {
      printf("Saved Image\n");
    }
    else  {
      printf("Save Failed\n");
    }

    // Free memory
    FreeImage_Unload(img);
    FreeImage_Unload(img_8b);
    FreeImage_Unload(res1_img);
    FreeImage_Unload(res2_img);
  }

  // release the FreeImage library
  FreeImage_DeInitialise();

  return 0;
}
