#include <octave/oct.h>
#include <Magick++.h>
#include <iostream>
using namespace std;
using namespace Magick;

octave_value_list read_indexed_images(vector<Image> imvec, Array<int> frameidx, bool wantalpha)
{
    octave_value_list output;
    int rows = imvec[0].baseRows();
    int columns = imvec[0].baseColumns();
    int nframes = frameidx.length();
    ImageType type = imvec[0].type();
    
    unsigned int mapsize = imvec[0].colorMapSize();
    int i = mapsize;
    unsigned int depth = 0;
    while(i >>= 1) depth++;
    i = 0;
    depth--;
    while(depth >>= 1) i++;
    depth = 1 << i;

    int x, y, frame;
    const IndexPacket *pix;
    switch(depth) {
	case 1:
	case 2:
	case 4:
	case 8:
	{
	    uint8NDArray im = uint8NDArray(dim_vector(rows, columns, nframes));
	    for(frame=0; frame < nframes; frame++) {
		imvec[frameidx(frame)].getConstPixels(0,0, columns, rows);
		pix = imvec[frameidx(frame)].getConstIndexes();
		i = 0;      
		for(y=0; y < rows; y++) {
		    for(x=0; x < columns; x++) {        
			im(y, x, frame) = static_cast<octave_uint8>(pix[i++]);
		    }
		}
	    }
	    im.chop_trailing_singletons();
	    output(0) = octave_value(im);
	}
	    break;
	case 16:
	{
	    uint16NDArray im = uint16NDArray(dim_vector(rows, columns, nframes));
	    for(frame=0; frame < nframes; frame++) {
		imvec[frameidx(frame)].getConstPixels(0,0, columns, rows);
		pix = imvec[frameidx(frame)].getConstIndexes();		
		i = 0;      
		for(y=0; y < rows; y++) {
		    for(x=0; x < columns; x++) {
			im(y, x, frame) = static_cast<octave_uint16>(pix[i++]);
		    }
		}
	    }
	    im.chop_trailing_singletons();
	    output(0) = octave_value(im);
	}
	    break;
	default:
	    error("Index depths bigger than 16-bit not supported");
	    return octave_value_list();
    }

    ColorRGB c;
    Matrix map = Matrix(mapsize, 3);
    Matrix alpha;
    switch(type) {
	case PaletteMatteType:
/*	    warning("palettematte");
	    map = Matrix(mapsize, 3);
	    alpha = Matrix(mapsize, 1);
	    for(i = 0; i < mapsize; i++) {
		warning("%d", i);
		c = imvec[0].colorMap(i);
		map(i, 0) = c.red();
		map(i, 1) = c.green();
		map(i, 2) = c.blue();
		alpha(i, 1) = c.alpha();				
	    }
	    break;	    */
    	case PaletteType:
	    alpha = Matrix(0,0);
	    for(i = 0; i < mapsize; i++) {
		c = imvec[0].colorMap(i);
		map(i, 0) = c.red();
		map(i, 1) = c.green();
		map(i, 2) = c.blue();		
	    }
	    break;	    
	default:
	    error("Unsupported indexed image type");
	    return octave_value_list();
    }

    output(1) = octave_value(map);
    if(wantalpha) {
	output(2) = octave_value(alpha);
    }     
    return output;
}

template <class T>
octave_value_list read_images(vector<Image> imvec, Array<int> frameidx)
{
  int i;
  T im;  
  int rows = imvec[0].baseRows();
  int columns = imvec[0].baseColumns();
  int nframes = frameidx.length();
  ImageType type = imvec[0].type();
  
  int x, y, frame;
  const PixelPacket *pix;
  dim_vector idim = dim_vector();
  idim.resize(4);
  idim(0) = rows;
  idim(1) = columns;
  idim(2) = 1;
  idim(3) = nframes;
  Array<int> idx(dim_vector(4));
  switch(type) {
      case BilevelType:
    //    break;
      case GrayscaleType:
	  im = T(dim_vector(rows, columns, nframes));
	  for(frame=0; frame < nframes; frame++) {
	      pix = imvec[frameidx(frame)].getConstPixels(0,0, columns, rows);
	      i = 0;      
	      for(y=0; y < rows; y++) {
		  for(x=0; x < columns; x++) {        
		      im(y, x, frame) = pix[i++].red;
		  }
	      }
	  }
	  break;
      case GrayscaleMatteType:
	  idim(2) = 2;
	  im = T(idim);
	  for(frame=0; frame < nframes; frame++) {
	      idx(3) = frame;
	      i = 0;
	      pix = imvec[frameidx(frame)].getConstPixels(0,0, columns, rows);
	      for(y=0; y < rows; y++) {
		  idx(0) = y;
		  for(x=0; x < columns; x++) {
		      idx(1) = x;
		      idx(2) = 0;
		      im(idx) = pix[i].red;
		      idx(2) = 1;
		      im(idx) = pix[i].opacity;
		      i++;
		  }
	      }
	  }    
	  break;
      case PaletteType:
      case TrueColorType:
	  idim(2) = 3;
	  im = T(idim);      
	  for(frame=0; frame < nframes; frame++) {
	      idx(3) = frame;
	      i = 0;
	      pix = imvec[frameidx(frame)].getConstPixels(0,0, columns, rows);
	      for(y=0; y < rows; y++) {
		  idx(0) = y;
		  for(x=0; x < columns; x++) {
		      idx(1) = x;
		      idx(2) = 0;
		      im(idx) = pix[i].red;
		      idx(2) = 1;
		      im(idx) = pix[i].green;
		      idx(2) = 2;
		      im(idx) = pix[i].blue;
		      i++;
		  }
	      }
	  }      
	  break;
      case PaletteMatteType:
      case TrueColorMatteType:
      case ColorSeparationType:
	  idim(2) = 4;
	  im = T(idim);      	  
	  for(frame=0; frame < nframes; frame++) {
	      idx(3) = frame;
	      i = 0;
	      pix = imvec[frameidx(frame)].getConstPixels(0,0, columns, rows);
	      for(y=0; y < rows; y++) {
		  idx(0) = y;
		  for(x=0; x < columns; x++) {
		      idx(1) = x;
		      idx(2) = 0;
		      im(idx) = pix[i].red;
		      idx(2) = 1;
		      im(idx) = pix[i].green;
		      idx(2) = 2;
		      im(idx) = pix[i].blue;
		      idx(2) = 3;
		      im(idx) = pix[i].opacity;
		      i++;
		  }
	      }
	  }      
	  break;
      default:
	  error("Undefined Imagemagick image type");
	  return octave_value_list();
  }

  im.chop_trailing_singletons();
  return octave_value_list(octave_value(im));
}

// instantiate templates
template octave_value_list read_images<boolNDArray>(vector<Image>, Array<int>);
template octave_value_list read_images<uint8NDArray>(vector<Image>, Array<int>);
template octave_value_list read_images<uint16NDArray>(vector<Image>, Array<int>);

DEFUN_DLD(__magick_read__, args, nargout,
"function m = __imagemagick_read__(fname[, index])\n\
function [m, colormap] = __imagemagick_read__(fname[, index])\n\
function [m, colormap, alpha] = __imagemagick_read__(fname[, index])\n\
\n\
Read images with ImageMagick++. User interface in imread.m.\n\
\n\
")
{
    octave_value_list output;
    int i;    
    if(args.length() > 2 || args.length() < 1 || !args(0).is_string() \
       || nargout > 3) {
      print_usage ("magick_read");
      return octave_value_list();
    }
    Array<int> frameidx;
    if(args.length() == 2 && args(1).is_real_type()) {
	frameidx = args(1).int_vector_value();
    } else {
	frameidx = Array<int>(1);
	frameidx(0) = 1;
    }

    vector<Image> imvec;
    try {
	// Read a file into vector of image objects
	readImages(&imvec, args(0).string_value());
    }
    catch( Warning &warning_ ) {
        warning("Magick++ warning: %s", warning_.what());
    }
    catch( ErrorCoder &error_) {
	warning("Magick++ coder error: %s", error_.what());
    }
    catch( Exception &error_ ) {
	error("Magick++ exception: %s", error_.what());
	imvec.clear();
	return octave_value_list();	
    }

    int nframes = imvec.size();
    for(i = 0; i < frameidx.length(); i++) {
	frameidx(i) = frameidx(i) - 1;
	if(frameidx(i) >= nframes || frameidx(i) < 0) {
	    error("Invalid index vector");
	    imvec.clear();
	    return output;
	}
    }
    
    ClassType klass = imvec[0].classType();
    if(klass == PseudoClass && nargout > 1) {
	output = read_indexed_images(imvec, frameidx, (nargout == 3));
    } else {
	int depth = imvec[0].modulusDepth();
	i = 0;
	while(depth >>= 1) i++;
	depth = 1 << i;
	
	switch(depth) {
	    case 1:
		output = read_images<boolNDArray>(imvec, frameidx);
	    break;
	    case 2:
	    case 4:
	    case 8:
		output = read_images<uint8NDArray>(imvec, frameidx);		
	    break;
	    case 16:
		output = read_images<uint16NDArray>(imvec, frameidx);		
	    break;
	    case 32:
	    case 64:
	    default:
		error("Image depths bigger than 16-bit not supported");
	}
	if(nargout > 1) {
	    output(1) = Matrix(0,0);
	    if(nargout > 2)
		output(2) = Matrix(0,0);
	}
    }
    imvec.clear();

    return output;
}


syntax highlighted by Code2HTML, v. 0.9.1