/* Copyright (c) 2011 Artjom Popov */

#import <AVFoundation/AVFoundation.h>
#import "NSString+Additions.h"
#import "UTScanner.h"
#import "UTScannerDelegate.h"
#import "UTScannerResult.h"
#import <zxing/qrcode/QRCodeReader.h>
#import <zxing/LuminanceSource.h>
#import <zxing/MultiFormatReader.h>
#import <zxing/BinaryBitmap.h>
#import <zxing/ReaderException.h>
#import <zxing/common/IllegalArgumentException.h>
#import <zxing/common/GlobalHistogramBinarizer.h>
#import <zxing/common/HybridBinarizer.h>

class GrayBytesMonochromeBitmapSource : public zxing::LuminanceSource {
private:
	int width_;
	int height_;
	const unsigned char *bytes_;
	int bytesPerRow_;
	
public:
	GrayBytesMonochromeBitmapSource(const unsigned char *bytes, 
									int width, 
									int height,
									int bytesPerRow);
	virtual ~GrayBytesMonochromeBitmapSource() { }
	
	virtual unsigned char getPixel(int x, int y);
	
	virtual int getWidth();
	virtual int getHeight();
	unsigned char* getRow(int y, unsigned char* row);
	unsigned char* getMatrix();
	
private:
	GrayBytesMonochromeBitmapSource(const GrayBytesMonochromeBitmapSource&);
	GrayBytesMonochromeBitmapSource& operator=(const GrayBytesMonochromeBitmapSource&);
};

GrayBytesMonochromeBitmapSource::GrayBytesMonochromeBitmapSource(const unsigned char *bytes, 
																 int width, 
																 int height,
																 int bytesPerRow)  : 
width_(width), 
height_(height),
bytes_(bytes), 
bytesPerRow_(bytesPerRow) { }


int GrayBytesMonochromeBitmapSource::getWidth() {
	return width_;
}

int GrayBytesMonochromeBitmapSource::getHeight() {
	return height_;
}

unsigned char* GrayBytesMonochromeBitmapSource::getRow(int y, unsigned char* row) {
	if(row == NULL) {
		row = new unsigned char[width_];
	}
	
	int offset = y * bytesPerRow_;
	memcpy(row, &bytes_[offset], width_);
	return row;
}

unsigned char* GrayBytesMonochromeBitmapSource::getMatrix() {
	int size = width_ * height_;
	unsigned char* result = new unsigned char[size];
	
	memcpy(result, bytes_, size);
	
	return result;
}

unsigned char GrayBytesMonochromeBitmapSource::getPixel(int x, int y) {
	/*	if (x >= width_ || y >= height_) {
	 throw new ReaderException("bitmap coordinate out of bounds");
	 }*/
	int index = y * bytesPerRow_ + x;
	return bytes_[index];
}

#pragma mark -

#define SUBSET_SIZE 320.0

@interface UTScannerOperation : NSOperation
{
	@private
	UTScanner *m_scanner;
	CVImageBufferRef m_buffer;
}

- (id)initWithScanner:(UTScanner *)scanner buffer:(CMSampleBufferRef)buffer;

@end

@implementation UTScannerOperation

- (id)initWithScanner:(UTScanner *)scanner buffer:(CMSampleBufferRef)buffer
{
	self = [super init];
	
	if(self) {
		self->m_scanner = [scanner retain];
		self->m_buffer = CVBufferRetain(CMSampleBufferGetImageBuffer(buffer));
		CVPixelBufferLockBaseAddress(self->m_buffer, 0); 
	}
	
	return self;
}

#pragma mark NSOperation

- (void)main
{
	CVImageBufferRef imageBuffer = (CVImageBufferRef)self->m_buffer;
	void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); 
	
	size_t width = CVPixelBufferGetWidth(imageBuffer);
	size_t height = CVPixelBufferGetHeight(imageBuffer);
	size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
	size_t dataSize = CVPixelBufferGetDataSize(imageBuffer); 
	
	//NSLog(@"Data size %d", dataSize);
	
	// Create image
	CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, baseAddress, dataSize, NULL);
	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
	
	CGImageRef imageRef = CGImageCreate(width,
										height,
										8,
										32,
										bytesPerRow,
										colorSpace,
										kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
										provider,
										NULL,
										true,
										kCGRenderingIntentDefault);

	CGColorSpaceRelease(colorSpace);
	CGDataProviderRelease(provider);
	
	//NSLog(@"decoding: image to decode is (%d x %d) (%d bytes/row)", width, height, bytesPerRow);
	
	bytesPerRow = width * sizeof(UInt8);
	
	unsigned char *buffer = (unsigned char *)malloc(bytesPerRow * height);
	//NSLog(@"allocated %d bytes of memory", bytesPerRow * height);
	
	// Render image into the buffer
	CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
	CGContextRef context = CGBitmapContextCreate(buffer, 
												 width, 
												 height, 
												 8, 
												 bytesPerRow, 
												 grayColorSpace, 
												 kCGImageAlphaNone);
	
	CGContextDrawImage(context, CGRectMake(0.0F, 0.0F, width, height), imageRef);

	CGColorSpaceRelease(grayColorSpace);
	CGContextRelease(context);
	CGImageRelease(imageRef);
	
	zxing::qrcode::QRCodeReader *reader = new zxing::qrcode::QRCodeReader();
	
	zxing::Ref<zxing::LuminanceSource> source (new GrayBytesMonochromeBitmapSource(buffer, width, height, bytesPerRow));
	//zxing::Ref<zxing::Binarizer> binarizer (new zxing::GlobalHistogramBinarizer(source));
	zxing::Ref<zxing::Binarizer> binarizer (new zxing::HybridBinarizer(source));
	zxing::Ref<zxing::BinaryBitmap> grayImage (new zxing::BinaryBitmap(binarizer));
	
	//NSLog(@"created GrayBytesMonochromeBitmapSource", width, height);
	//NSLog(@"grayImage count = %d", grayImage->count());
	
	NSString *decoderResult = nil;
	UTScannerReaderError readerError = UTScannerReaderNoError;
	
	try {
		//NSLog(@"decoding gray image");
		
		zxing::Ref<zxing::Result> result(reader->decode(grayImage, zxing::DecodeHints(1 << zxing::BarcodeFormat_QR_CODE)));
		
		//NSLog(@"gray image decoded");
		
		zxing::Ref<zxing::String> resultText(result->getText());
		const char *cString = resultText->getText().c_str();
		std::vector<zxing::Ref<zxing::ResultPoint> > resultPoints = result->getResultPoints();
		NSMutableArray *points = 
		[NSMutableArray arrayWithCapacity:resultPoints.size()];
		
		for (size_t i = 0; i < resultPoints.size(); i++) {
			zxing::Ref<zxing::ResultPoint> rp(resultPoints[i]);
			CGPoint p = CGPointMake(rp->getX(), rp->getY());
			[points addObject:[NSValue valueWithCGPoint:p]];
		}
		
		decoderResult = [NSString stringWithCString:cString
													encoding:NSUTF8StringEncoding];
		
		if(decoderResult) {
			NSLog(@"Result: %@", decoderResult);
		} else {
			decoderResult = nil;
			throw zxing::ReaderException("Decoding error");
		}

	} catch(zxing::ReaderException rex) {
		NSLog(@"failed to decode, caught ReaderException '%s'",
			 rex.what());
		
		NSString *message = [NSString stringWithCString:rex.what() encoding:NSUTF8StringEncoding];
		
		if ([message hasPrefix:@"Transformed point out of bounds"]) {
			readerError = UTScannerReaderTransformedPointOutOfBoundsError;
		} else if ([message isEqualToString:@"Error locator degree does not match number of roots"]) {
			readerError = UTScannerReaderLocatorDegreeError;
		} else if ([message isEqualToString:@"Could not find three finder patterns"]) {
			readerError = UTScannerReaderFinderPatternsError;
		} else {
			readerError = UTScannerReaderUnknownError;
		}
	} catch(zxing::IllegalArgumentException iex) {
		NSLog(@"failed to decode, caught IllegalArgumentException '%s'", 
			 iex.what());
	} catch(...) {
		NSLog(@"Caught unknown exception!");
	}
	
	if (decoderResult) {
		[self->m_scanner performSelectorOnMainThread:@selector(didEndScanOperation:)
										  withObject:decoderResult
									   waitUntilDone:NO];
	}
	
	[self->m_scanner performSelectorOnMainThread:@selector(scanOperationStatus:)
									  withObject:[NSNumber numberWithInt:readerError]
								   waitUntilDone:NO];
	
	delete reader;
	free(buffer);
	
	//NSLog(@"finished decoding.");
}

#pragma mark NSObject

- (void)dealloc
{
	CVPixelBufferUnlockBaseAddress(self->m_buffer, 0); 
	CVBufferRelease(self->m_buffer);
	[self->m_scanner release];
	
	[super dealloc];
}

@end

#pragma mark -

@implementation UTScanner


@dynamic delegate;

- (id <UTScannerDelegate>)delegate
{
	return self->m_delegate;
}

- (void)setDelegate:(id <UTScannerDelegate>)delegate
{
	if(self->m_delegate != delegate) {
		self->m_delegate = delegate;
		
		[self->m_queue cancelAllOperations];
		[self->m_queue waitUntilAllOperationsAreFinished];
	}
}

- (void)scanSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
	UTScannerOperation *operation = [[UTScannerOperation alloc] initWithScanner:self buffer:sampleBuffer];
	
	[self->m_queue cancelAllOperations];
	[self->m_queue addOperation:operation];
	[operation release];
}

- (void)didEndScanOperation:(NSString *)result
{
	//NSLog(@"Found: %@", code);
	[self->m_delegate scanner:self didReadWithResult:result];
}

- (void)scanOperationStatus:(NSNumber *)error
{
	[self->m_delegate scanner:self didFailWithErrorCode:(UTScannerReaderError)[error integerValue]];
}

#pragma mark NSObject

- (id)init
{
	self = [super init];
	
	if(self) {
		self->m_queue = [[NSOperationQueue alloc] init];
		self->m_queue.maxConcurrentOperationCount = 1;
	}
	
	return self;
}

- (void)dealloc
{
	[self->m_queue cancelAllOperations];
	[self->m_queue waitUntilAllOperationsAreFinished];
	[self->m_queue release];
	
	[super dealloc];
}

@end
