//
//  DrawViewController.m
//  FinalProject
//
//  Created by Sean Campbell on 12/8/13.
//  Copyright (c) 2013 Sean Campbell. All rights reserved.
//

#import "DrawViewController.h"
#import "HighScore.h"
#import <Foundation/NSString.h>

// Width and height for template
// and drawing images
#define IMAGE_WIDTH 300
#define IMAGE_HEIGHT 300

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height

@implementation DrawViewController

@synthesize symbolSet;
@synthesize isAdvancing;
@synthesize symbols;
@synthesize symbolIndex;
@synthesize managedObjectContext;

- (id)init
{
    if (self = [super init]) {
        isAdvancing = NO;
        
        UIBarButtonItem *submitButtonItem = [[UIBarButtonItem alloc] init];
        submitButtonItem.title = @"Done";
        submitButtonItem.target = self;
        submitButtonItem.action = @selector(submit);
        self.navigationItem.rightBarButtonItem = submitButtonItem;

        UIBarButtonItem *mainScreenButtonItem = [[UIBarButtonItem alloc] init];
        mainScreenButtonItem.title = @"Main Menu";
        mainScreenButtonItem.target = self;
        mainScreenButtonItem.action = @selector(goToMainScreen);
        self.navigationItem.leftBarButtonItem = mainScreenButtonItem;
        
        templateImageView = [[UIImageView alloc] initWithFrame: [UIScreen mainScreen].bounds];
        drawingImageView = [[UIImageView alloc] initWithFrame: [UIScreen mainScreen].bounds];
        
        self.view.backgroundColor = [UIColor whiteColor];
        
        [self.view addSubview: templateImageView];
        [self.view addSubview: drawingImageView];
    }
    return self;
}


- (void)viewWillAppear: (BOOL) animated
{
    [super viewWillAppear: animated];

    // Don't show the back button if the user is playing
    // through a set, because that will let him/her go
    // back to the last symbol in the set.
    if (self.isAdvancing)
        self.navigationItem.leftItemsSupplementBackButton = NO;
    else
        self.navigationItem.leftItemsSupplementBackButton = YES;

    // Convert the symbol name into a file name.
    // (1) Replace spaces with underscores
    // (2) Add .png at the end
    // (3) Make it lowercase
    NSString *imageName = [[[[symbols objectAtIndex: symbolIndex] stringByReplacingOccurrencesOfString: @" " withString: @"_"] stringByAppendingString: @".png"] lowercaseString];
    
    // Load the image into a temporary UIImage.
    UIImage *tempImage = [UIImage imageNamed:imageName];
    
    // Now we will draw the loaded image onto a bigger image with a transparent background.
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT), NO, 1.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    // Make the drawing image simply a transparent image of the right size.
    drawingImage = UIGraphicsGetImageFromCurrentImageContext();
    // Flip the image so the coordinate systems are matching.
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, 0.0, SCREEN_HEIGHT);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGRectMake((SCREEN_WIDTH - IMAGE_WIDTH) / 2, (SCREEN_HEIGHT - IMAGE_HEIGHT) / 2, IMAGE_WIDTH, IMAGE_HEIGHT), tempImage.CGImage);
    CGContextRestoreGState(context);
    // This image will be the template.
    templateImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [templateImageView setImage: templateImage];
    
    startTime = nil;
    endTime = nil;
}

- (void)viewWillDisappear: (BOOL) animated
{
    // Clear the images and release them from memory.
    templateImage = nil;
    drawingImage = nil;
    templateImageView.image = nil;
    drawingImageView.image = nil;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    templateImageView = nil;
    drawingImageView = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
}

- (void)drawLineFrom: (CGPoint)start To: (CGPoint)end
{
    // Initiate the context to be the size of the screen.
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT), NO, 1.0);
    drawingContext = UIGraphicsGetCurrentContext();
    CGContextSetLineCap(drawingContext, kCGLineCapRound);
    CGContextSetRGBStrokeColor(drawingContext, 0.0, 0.0, 0.7, 1.0);
    CGContextSetLineWidth(drawingContext, 35);
    // Flip the image so the coordinate systems line up.
    CGContextSaveGState(drawingContext);
    CGContextTranslateCTM(drawingContext, 0.0, SCREEN_HEIGHT);
    CGContextScaleCTM(drawingContext, 1.0, -1.0);
    CGContextDrawImage(drawingContext, [UIScreen mainScreen].bounds, drawingImage.CGImage);
    CGContextRestoreGState(drawingContext);
    // Draw from old point to new point;
    CGContextMoveToPoint(drawingContext, start.x, start.y);
    CGContextAddLineToPoint(drawingContext, end.x, end.y);
    CGContextStrokePath(drawingContext);
    // Get the newly drawn image.
    drawingImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    drawingImageView.image = drawingImage;
}

- (int)calculateAccuracy
{
    // Based on code from http://stackoverflow.com/questions/10412206/how-to-get-the-raw-pixel-data-from-cgimage
    CFDataRef templateRawData = CGDataProviderCopyData(CGImageGetDataProvider(templateImage.CGImage));
    UInt8 *templateBuffer = (UInt8 *)CFDataGetBytePtr(templateRawData);
    CFDataRef drawingRawData = CGDataProviderCopyData(CGImageGetDataProvider(drawingImage.CGImage));
    UInt8 *drawingBuffer = (UInt8 *)CFDataGetBytePtr(drawingRawData);
    int length = CFDataGetLength(templateRawData);
    
    int possibleScore = 0;
    int score = 0;
    
    for (int i = 0; i < length; i += 4) {
        int ta = templateBuffer[i+3];
        int da = drawingBuffer[i+3];
        
        // The score is calculated as follows:
        // If the template is colored, but not the drawing...
        //      The possible score increases by 2, but the score is not affected.
        // If the drawing is colored, but not the template...
        //      Subtract 1 from the score.
        //      Add 2 to the possible score.
        // If both are colored...
        //      Add 2 to score.
        //      Add 2 to possible score.
        if (ta > 0 && da > 0) {
            possibleScore += 2;
            score += 2;
        } else if (ta > 0) {
            possibleScore += 2;
        } else if (da > 0){
            score -= 1;
        }
    }
    CFRelease(templateRawData);
    CFRelease(drawingRawData);
    
    double percentScore = (double)score / possibleScore * 100;
    return (int)percentScore;
}

#pragma mark Navigation button selectors
- (void)submit
{
    if (startTime == nil)
        startTime = [NSDate date];
    
    NSTimeInterval timeElapsed = [endTime timeIntervalSinceDate: startTime];
    int accuracy = [self calculateAccuracy];
    
    // Adjust the score depending on the time it took.
    float factor;
    if (timeElapsed < 2) {
        factor = 1;
    } else if (timeElapsed < 4) {
        factor = 0.9;
    } else if (timeElapsed < 6) {
        factor = 0.8;
    } else {
        factor = 0.7;
    }
    double score = factor * accuracy;
    // Don't allow negative scores or
    // accuracies.
    if (accuracy < 0)
        accuracy = 0;
    if (score < 0)
        score = 0;
    
    // Update cumulative variables if the user is
    // playing through the set.
    if (isAdvancing) {
        self.cumTimeElapsed += timeElapsed;
        self.cumAccuracy += accuracy;
        self.cumScore += score;
    }

    // Send a request to the database.
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"HighScores" inManagedObjectContext:managedObjectContext];
    request.entity = entity;
    // Store high scores from the database into the high scores array.
    NSError *fetchError = nil;
    NSArray *highScores = [[managedObjectContext executeFetchRequest:request error:&fetchError] mutableCopy];
    // If there is already a score for this set in the database, replaced the
    // information on that one. Don't create a new one.
    HighScore *highScore = nil;
    for (HighScore *score in highScores) {
        if ([score.symbol isEqualToString:[NSString stringWithFormat: @"%@ Symbol", [self.symbols objectAtIndex:symbolIndex ]]]) {
            highScore = score;
            break;
        }
    }
    // Clean up memory.
    request = nil;
    
    // If no score was already in the database, create a new entry.
    if (highScore == nil)
        highScore = [NSEntityDescription insertNewObjectForEntityForName: @"HighScores" inManagedObjectContext: managedObjectContext];
    // If there was an entry but its score is higher than this one,
    // don't update the information.
    if (highScore.symbol == nil || [highScore.score doubleValue] < score) {
        // Update the database.
        highScore.date = startTime;
    	highScore.symbol = [NSString stringWithFormat:@"%@ Symbol", [symbols objectAtIndex:symbolIndex] ];
    	highScore.timeElapsed = [NSNumber numberWithDouble:timeElapsed];
        highScore.accuracy = [NSNumber numberWithInt:accuracy];
        highScore.score = [NSNumber numberWithDouble:score];
        highScore.symbolSet = [NSString stringWithFormat:@"%@ Set", self.symbolSet];
    
        NSError *error = nil;
        [managedObjectContext save: &error];
    }
    highScore = nil;
    
    if (isAdvancing) {
        // Prompt the user with the results so far.
        NSString *message = [NSString stringWithFormat:@"Time to draw symbol: %.2fs\nAccuracy: %d.\nScore for this symbol: %.2f\nAverage score so far: %.2f", timeElapsed, accuracy, score, self.cumScore / (self.symbolIndex + 1)];
        UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Submit" message:message delegate:self cancelButtonTitle:@"Next" otherButtonTitles: nil];
        [dialog show];
        dialog = nil;
        message = nil;
    } else {
        // Prompt the user to see if he/she wants to redo the symbol or go back.
        NSString *message = [NSString stringWithFormat:@"Time to draw symbol: %.2fs\nAccuracy: %d.\nFinal score: %.2f.\nDo you want to redo this symbol or go back to the set menu?", timeElapsed, accuracy, score];
        UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Submit" message:message delegate:self cancelButtonTitle:@"Redo" otherButtonTitles:@"Back", nil];
        [dialog show];
        dialog = nil;
        message = nil;
    }
}

- (void)goToMainScreen
{
    [self.navigationController popToRootViewControllerAnimated:YES];
}

- (void)addScoreToDatabase
{
    // Send a request to the database.
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"HighScores" inManagedObjectContext:managedObjectContext];
    request.entity = entity;
    // Store high scores from the database into the high scores array.
    NSError *fetchError = nil;
    NSArray *highScores = [[managedObjectContext executeFetchRequest:request error:&fetchError] mutableCopy];
    // If there is already a score for this set in the database, replaced the
    // information on that one. Don't create a new one.
    HighScore *highScore = nil;
    for (HighScore *score in highScores) {
        if ([score.symbol isEqualToString:[NSString stringWithFormat: @"%@ Set", self.symbolSet]]) {
            highScore = score;
            break;
        }
    }
    // Clean up memory.
    request = nil;
    
    // If there was an entry but its score is higher than this one,
    // don't update the information.    
    if ([highScore.score doubleValue] < self.cumScore / symbols.count) {
        // Put averages of the time elapsed, accuracy, and score into the database.
        highScore.date = endTime;
        highScore.symbol = [NSString stringWithFormat: @"%@ Set", self.symbolSet];
        highScore.timeElapsed = [NSNumber numberWithDouble:self.cumTimeElapsed / symbols.count];
        highScore.accuracy = [NSNumber numberWithInt:self.cumAccuracy / symbols.count];
        highScore.score = [NSNumber numberWithDouble:self.cumScore / symbols.count];
        highScore.symbolSet = nil;
    
        NSError *error = nil;
        [managedObjectContext save: &error];
    }
}

#pragma mark Alert view
- (void)alertView: (UIAlertView *)alertView clickedButtonAtIndex: (NSInteger)buttonIndex
{
    // Is the user playing through a set of symbols
    // or only working on one symbol?
    if (isAdvancing) {
        // If the user has clicked next to go to the next symbol,
        // then prepare the next view controller for that.
        // If the user has clicked OK after finishing the set, take
        // him/her back to the main menu.
        if (symbolIndex < symbols.count) {
            // If the user is not done with the set, go to
            // the next symbol.
            // If the user is done, display an alert view
            // that lets him/her know.
            if (symbolIndex < symbols.count - 1) {
                // Create a new controller with the next symbol.
                DrawViewController *drawCtrl = [[DrawViewController alloc] init];
                drawCtrl.symbolSet = self.symbolSet;
                drawCtrl.symbols = self.symbols;
                drawCtrl.symbolIndex = self.symbolIndex + 1;
                drawCtrl.managedObjectContext = self.managedObjectContext;
                drawCtrl.isAdvancing = YES;
                drawCtrl.cumScore = self.cumScore;
                drawCtrl.cumAccuracy = self.cumAccuracy;
                drawCtrl.cumTimeElapsed = self.cumTimeElapsed;
                [self.navigationController pushViewController:drawCtrl animated:YES];
            } else {
                [self addScoreToDatabase];
                NSString *results = [NSString stringWithFormat: @"Congratulations! You have finished this set. You can go back to the main screen and complete the other sets.\nAverage time per symbol: %.2fs\nAverage accuracy: %d\nAverage score: %.2f", self.cumTimeElapsed / symbols.count, self.cumAccuracy / symbols.count, self.cumScore / symbols.count];
                UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Congratulations!" message:results delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
                [dialog show];
                dialog = nil;
                results = nil;
                // Increment symbol index to differentiate from if the user
                // wants to redo the letter. This way we don't have to keep
                // track of the alert views to know which dialog is calling
                // this selector.
                symbolIndex++;
            }
        } else {
            // The user has finished the set of symbols and will be taken
            // back to the main menu.
            [self.navigationController popToRootViewControllerAnimated:YES];
        }
    } else {
        // Either redo the symbol or go back to the set menu,
        // depending on what button the user chose.
        if (buttonIndex == 0) {
            [self redoSymbol];
        } else {
            [self.navigationController popViewControllerAnimated:YES];
        }
    }
}

- (void)redoSymbol
{
    // Reset the drawing image to be completely transparent and reset the start and end times.
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT), NO, 1.0);
    drawingImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [drawingImageView setImage:drawingImage];
    
    startTime = nil;
    endTime = nil;
}


#pragma mark Touch controls
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];

    // If the user only touches the screen, a dot will be drawn.
    startPoint = [[touches anyObject] locationInView:drawingImageView];
    [self drawLineFrom:startPoint To:startPoint];
        
    // Only set the start time if it has not been set already.
    // This prevents the startTime from being reset when the
    // user makes a second stroke (for example, writing the letter 'i').
    if (startTime == nil)
        startTime = [NSDate date];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    
    CGPoint curPoint = [[touches anyObject] locationInView:drawingImageView];
    [self drawLineFrom:startPoint To:curPoint];
    startPoint = curPoint;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    endTime = [NSDate date];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
}

@end
