//
//  $Id$
//
//  SPExtendedTableInfo.m
//  sequel-pro
//
//  Created by Jason Hallford (jason.hallford@byu.edu) on Th July 08 2004.
//  sequel-pro Copyright (c) 2002-2003 Lorenz Textor. All rights reserved.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  More info at <http://code.google.com/p/sequel-pro/>

#import "SPExtendedTableInfo.h"
#import "SPTableData.h"
#import "RegexKitLite.h"
#import "SPDatabaseData.h"
#import "SPStringAdditions.h"
#import "SPConstants.h"
#import "TableDocument.h"
#import "TablesList.h"
#import "SPAlertSheets.h"
#import "TableSource.h"

@interface SPExtendedTableInfo (PrivateAPI)

- (NSString *)_formatValueWithKey:(NSString *)key inDictionary:(NSDictionary *)statusDict withLabel:(NSString *)label;

@end

@implementation SPExtendedTableInfo

@synthesize connection;

/**
 * Upon awakening bind the create syntax text view's background colour.
 */
- (void)awakeFromNib
{
	[tableCreateSyntaxTextView setAllowsDocumentBackgroundColorChange:YES];
	
	NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
	
	[bindingOptions setObject:NSUnarchiveFromDataTransformerName forKey:@"NSValueTransformerName"];
	
	[tableCreateSyntaxTextView bind:@"backgroundColor"
						   toObject:[NSUserDefaultsController sharedUserDefaultsController]
						withKeyPath:@"values.CustomQueryEditorBackgroundColor"
							options:bindingOptions];

	// Add observers for document task activity
	[[NSNotificationCenter defaultCenter] addObserver:self
											 selector:@selector(startDocumentTaskForTab:)
												 name:SPDocumentTaskStartNotification
											   object:tableDocumentInstance];
	[[NSNotificationCenter defaultCenter] addObserver:self
											 selector:@selector(endDocumentTaskForTab:)
												 name:SPDocumentTaskEndNotification
											   object:tableDocumentInstance];
}

#pragma mark -
#pragma mark IBAction methods

/**
 * Reloads the info for the currently selected table.
 */
- (IBAction)reloadTable:(id)sender
{
	// Reset the table data's cache
	[tableDataInstance resetAllData];
	
	// Load the new table info
	[self loadTable:selectedTable];
}

/**
 * Update the table type (storage engine) of the currently selected table.
 */
- (IBAction)updateTableType:(id)sender
{
	NSString *newType = [sender titleOfSelectedItem];
	NSString *currentType = [tableDataInstance statusValueForKey:@"Engine"];

	// Check if the user selected the same type
	if ([currentType isEqualToString:newType]) {
		return;
	}
	
	// Alter table's storage type
	[connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ TYPE = %@", [selectedTable backtickQuotedString], newType]];
	
	if ([connection getLastErrorID] == 0) {
		// Reload the table's data
		[tablesListInstance updateSelectionWithTaskString:NSLocalizedString(@"Reloading data...", @"Reloading data task description")];
	}
	else {
		[sender selectItemWithTitle:currentType];
		
		SPBeginAlertSheet(NSLocalizedString(@"Error changing table type", @"error changing table type message"), 
						  NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
						  [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table type to '%@'.\n\nMySQL said: %@", @"error changing table type informative message"), newType, [connection getLastErrorMessage]]);
	}
}

/**
 * Update the character set encoding of the currently selected table.
 */
- (IBAction)updateTableEncoding:(id)sender
{
	NSString *currentEncoding = [tableDataInstance tableEncoding];
	NSString *newEncoding = [[sender titleOfSelectedItem] stringByMatching:@"^.+\\((.+)\\)$" capture:1L];
	
	// Check if the user selected the same encoding
	if ([currentEncoding isEqualToString:newEncoding]) return;
	
	// Alter table's character set encoding
	[connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ CHARACTER SET = %@", [selectedTable backtickQuotedString], newEncoding]];
	
	if ([connection getLastErrorID] == 0) {
		// Reload the table's data
		[self reloadTable:self];
	}
	else {
		[sender selectItemWithTitle:currentEncoding];
		
		SPBeginAlertSheet(NSLocalizedString(@"Error changing table encoding", @"error changing table encoding message"), 
						  NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
						  [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table encoding to '%@'.\n\nMySQL said: %@", @"error changing table encoding informative message"), newEncoding, [connection getLastErrorMessage]]);
	}
}

/**
 * Update the character set collation of the currently selected table.
 */
- (IBAction)updateTableCollation:(id)sender
{
	NSString *newCollation = [sender titleOfSelectedItem];
	NSString *currentCollation = [tableDataInstance statusValueForKey:@"Collation"];
	
	// Check if the user selected the same collation
	if ([currentCollation isEqualToString:newCollation]) return;
	
	// Alter table's character set collation
	[connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ COLLATE = %@", [selectedTable backtickQuotedString], newCollation]];
	
	if ([connection getLastErrorID] == 0) {
		// Reload the table's data
		[self reloadTable:self];
	}
	else {
		[sender selectItemWithTitle:currentCollation];
		
		SPBeginAlertSheet(NSLocalizedString(@"Error changing table collation", @"error changing table collation message"), 
						  NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
						  [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table collation to '%@'.\n\nMySQL said: %@", @"error changing table collation informative message"), newCollation, [connection getLastErrorMessage]]);
	}
}

- (IBAction)resetAutoIncrement:(id)sender
{

	if([sender tag] == 1) {
		[tableRowAutoIncrement setEditable:YES];
		[tableRowAutoIncrement selectText:nil];
	} else {
		[tableRowAutoIncrement setEditable:NO];
		[tableSourceInstance resetAutoIncrement:sender];
	}
}

- (IBAction)resetAutoIncrementValueWasEdited:(id)sender
{
	[tableRowAutoIncrement setEditable:NO];
	[tableSourceInstance setAutoIncrementTo:[[tableRowAutoIncrement stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}

- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command 
{

	// Listen to ESC to abort editing of auto increment input field
	if (command == @selector(cancelOperation:) && control == tableRowAutoIncrement) {
		[tableRowAutoIncrement abortEditing];
		return YES;
	}
	return NO;
}

#pragma mark -
#pragma mark Other
 
/**
 * Load all the info for the supplied table by querying the table data instance and updaing the interface 
 * elements accordingly.
 * Note that interface elements are also toggled in start/endDocumentTaskForTab:, with similar logic.
 * Due to the large quantity of interface interaction in this function it is not thread-safe.
 */
- (void)loadTable:(NSString *)table
{	
	BOOL enableInteraction = ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableInfo] || ![tableDocumentInstance isWorking];

	[resetAutoIncrementResetButton setHidden:YES];

	// Store the table name away for future use
	selectedTable = table;
	
	// Retrieve the table status information via the table data cache
	NSDictionary *statusFields = [tableDataInstance statusValues];
	
	[tableTypePopUpButton removeAllItems];
	[tableEncodingPopUpButton removeAllItems];
	[tableCollationPopUpButton removeAllItems];
	
	// No table selected or view selected
	if ((!table) || [table isEqualToString:@""] || [[statusFields objectForKey:@"Engine"] isEqualToString:@"View"]) {
		
		[tableTypePopUpButton setEnabled:NO];
		[tableEncodingPopUpButton setEnabled:NO];
		[tableCollationPopUpButton setEnabled:NO];
		
		if ([[statusFields objectForKey:@"Engine"] isEqualToString:@"View"]) {
			[tableTypePopUpButton addItemWithTitle:@"View"];
			// Set create syntax
			[tableCreateSyntaxTextView setEditable:YES];
			[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCreateSyntaxTextView string] length]) replacementString:@""];
			[tableCreateSyntaxTextView setString:@""];
			NSString *createViewSyntax = [[tableDataInstance tableCreateSyntax] createViewSyntaxPrettifier];
			[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:createViewSyntax];
			[tableCreateSyntaxTextView insertText:createViewSyntax];
			[tableCreateSyntaxTextView didChangeText];
			[tableCreateSyntaxTextView setEditable:NO];
		} else {
			[tableCreateSyntaxTextView setEditable:YES];
			[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCreateSyntaxTextView string] length]) replacementString:@""];
			[tableCreateSyntaxTextView setString:@""];
			[tableCreateSyntaxTextView didChangeText];
			[tableCreateSyntaxTextView setEditable:NO];
		}
		
		[tableCreatedAt setStringValue:NSLocalizedString(@"Created at: ", @"table info created at label")];
		[tableUpdatedAt setStringValue:NSLocalizedString(@"Updated at: ", @"table info updated at label")];
		
		// Set row values
		[tableRowNumber setStringValue:NSLocalizedString(@"Number of rows: ", @"table info number of rows label")];
		[tableRowFormat setStringValue:NSLocalizedString(@"Row format: ", @"table info row format label")];	
		[tableRowAvgLength setStringValue:NSLocalizedString(@"Avg. row length: ", @"table info average row length label")];
		[tableRowAutoIncrement setStringValue:@""];
		
		// Set size values
		[tableDataSize setStringValue:NSLocalizedString(@"Data size: ", @"table info data size label")];
		[tableMaxDataSize setStringValue:NSLocalizedString(@"Max data size: ", @"table info max data size label")];	
		[tableIndexSize setStringValue:NSLocalizedString(@"Index size: ", @"table info index size label")]; 
		[tableSizeFree setStringValue:NSLocalizedString(@"Free data size: ", @"table info free data size label")];
		
		// Set comments 
		[tableCommentsTextView setEditable:NO];
		[tableCommentsTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCommentsTextView string] length]) replacementString:@""];
		[tableCommentsTextView setString:@""];
		[tableCommentsTextView didChangeText];
		
		return;
	}
	
	NSArray *engines    = [databaseDataInstance getDatabaseStorageEngines];
	NSArray *encodings  = [databaseDataInstance getDatabaseCharacterSetEncodings];
	NSArray *collations = [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]];
	
	if (([engines count] > 0) && ([statusFields objectForKey:@"Engine"])) {
		// Populate type popup button
		for (NSDictionary *engine in engines)
		{		
			[tableTypePopUpButton addItemWithTitle:[engine objectForKey:@"Engine"]];
		}	
		
		[tableTypePopUpButton selectItemWithTitle:[statusFields objectForKey:@"Engine"]];
		[tableTypePopUpButton setEnabled:enableInteraction];
	}
	else {
		[tableTypePopUpButton addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")];
	}
	
	if (([encodings count] > 0) && ([tableDataInstance tableEncoding])) {
		NSString *selectedTitle = @"";
		
		// Populate encoding popup button
		for (NSDictionary *encoding in encodings)
		{			
			NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]];
						
			[tableEncodingPopUpButton addItemWithTitle:menuItemTitle];
			
			if ([[tableDataInstance tableEncoding] isEqualToString:[encoding objectForKey:@"CHARACTER_SET_NAME"]]) {
				selectedTitle = menuItemTitle;
			}
		}	
		
		[tableEncodingPopUpButton selectItemWithTitle:selectedTitle];
		[tableEncodingPopUpButton setEnabled:enableInteraction];
	}
	else {
		[tableEncodingPopUpButton addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")];
	}
	
	if (([collations count] > 0) && ([statusFields objectForKey:@"Collation"])) {
		// Populate collation popup button
		for (NSDictionary *collation in collations)
		{		
			[tableCollationPopUpButton addItemWithTitle:[collation objectForKey:@"COLLATION_NAME"]];
		}	
				
		[tableCollationPopUpButton selectItemWithTitle:[statusFields objectForKey:@"Collation"]];
		[tableCollationPopUpButton setEnabled:enableInteraction];
	}
	else {
		[tableCollationPopUpButton addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")];
	}
	
	[tableCreatedAt setStringValue:[self _formatValueWithKey:@"Create_time" inDictionary:statusFields withLabel:@"Created at"]];
	[tableUpdatedAt setStringValue:[self _formatValueWithKey:@"Update_time" inDictionary:statusFields withLabel:@"Updated at"]];
	
	// Set row values
	[tableRowNumber setStringValue:[self _formatValueWithKey:@"Rows" inDictionary:statusFields withLabel:@"Number of rows"]];
	[tableRowFormat setStringValue:[self _formatValueWithKey:@"Row_format" inDictionary:statusFields withLabel:@"Row format"]];	
	[tableRowAvgLength setStringValue:[self _formatValueWithKey:@"Avg_row_length" inDictionary:statusFields withLabel:@"Avg. row length"]];
	[tableRowAutoIncrement setStringValue:[self _formatValueWithKey:@"Auto_increment" inDictionary:statusFields withLabel:@"Auto increment"]];
	
	// Set size values
	[tableDataSize setStringValue:[self _formatValueWithKey:@"Data_length" inDictionary:statusFields withLabel:@"Data size"]]; 
	[tableMaxDataSize setStringValue:[self _formatValueWithKey:@"Max_data_length" inDictionary:statusFields withLabel:@"Max data size"]];	
	[tableIndexSize setStringValue:[self _formatValueWithKey:@"Index_length" inDictionary:statusFields withLabel:@"Index size"]]; 
	[tableSizeFree setStringValue:[self _formatValueWithKey:@"Data_free" inDictionary:statusFields withLabel:@"Free data size"]];	 
	
	// Set comments
	[tableCommentsTextView setEditable:YES];
	[tableCommentsTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCommentsTextView string] length]) replacementString:[statusFields objectForKey:@"Comment"]];
	[tableCommentsTextView setString:[statusFields objectForKey:@"Comment"]];
	[tableCommentsTextView didChangeText];
	[tableCommentsTextView setEditable:enableInteraction];
	
	// Set create syntax
	[tableCreateSyntaxTextView setEditable:YES];
	[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCommentsTextView string] length]) replacementString:@""];
	[tableCreateSyntaxTextView setString:@""];
	[tableCreateSyntaxTextView didChangeText];
	[tableCreateSyntaxTextView shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:[tableDataInstance tableCreateSyntax]];
	[tableCreateSyntaxTextView insertText:[tableDataInstance tableCreateSyntax]];
	[tableCreateSyntaxTextView didChangeText];
	[tableCreateSyntaxTextView setEditable:NO];
	
	// Validate Reset AUTO_INCREMENT button
	if ([statusFields objectForKey:@"Auto_increment"] && ![[statusFields objectForKey:@"Auto_increment"] isKindOfClass:[NSNull class]]) {
		[resetAutoIncrementResetButton setHidden:NO];
	}
}

/**
 * NSTextView delegate. Used to change the selected table's comment.
 */
- (void)textDidEndEditing:(NSNotification *)notification
{
	id object = [notification object];
	if ((object == tableCommentsTextView) && ([object isEditable]) && ([selectedTable length] > 0)) {
		
		NSString *currentComment = [tableDataInstance statusValueForKey:@"Comment"];
		NSString *newComment = [[tableCommentsTextView string] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

		// Check that the user actually changed the tables comment
		if (![currentComment isEqualToString:newComment]) {
			
			// Alter table's comment
			[connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ COMMENT = '%@'", [selectedTable backtickQuotedString], [connection prepareString:newComment]]];
			
			if ([connection getLastErrorID] == 0) {
				// Reload the table's data
				[self reloadTable:self];
			}
			else {
				SPBeginAlertSheet(NSLocalizedString(@"Error changing table comment", @"error changing table comment message"), 
								  NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil,
								  [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table's comment to '%@'.\n\nMySQL said: %@", @"error changing table comment informative message"), newComment, [connection getLastErrorMessage]]);
			}
		}
	}
}

#pragma mark -
#pragma mark Task interaction

/**
 * Disable all content interactive elements during an ongoing task.
 */
- (void)startDocumentTaskForTab:(NSNotification *)aNotification
{
	// Only proceed if this view is selected.
	if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableInfo])
		return;

	[tableTypePopUpButton setEnabled:NO];
	[tableEncodingPopUpButton setEnabled:NO];
	[tableCollationPopUpButton setEnabled:NO];
	[tableCommentsTextView setEditable:NO];
}

/**
 * Enable all content interactive elements after an ongoing task.
 */
- (void)endDocumentTaskForTab:(NSNotification *)aNotification
{
	// Only proceed if this view is selected.
	if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableInfo])
		return;

	NSDictionary *statusFields = [tableDataInstance statusValues];
	
	if (!selectedTable || ![selectedTable length] || [[statusFields objectForKey:@"Engine"] isEqualToString:@"View"])
		return;

	// If we are viewing tables in the information_schema database, then disable all controls that cause table
	// changes as these tables are not modifiable by anyone.
	BOOL isInformationSchemaDb = [[tableDocumentInstance database] isEqualToString:@"information_schema"];
	
	if ([[databaseDataInstance getDatabaseStorageEngines] count] && [statusFields objectForKey:@"Engine"]) {
		[tableTypePopUpButton setEnabled:(!isInformationSchemaDb)];
	}

	if ([[databaseDataInstance getDatabaseCharacterSetEncodings] count] && [tableDataInstance tableEncoding]) {
		[tableEncodingPopUpButton setEnabled:(!isInformationSchemaDb)];
	}

	if ([[databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]] count]
		&& [statusFields objectForKey:@"Collation"])
	{
		[tableCollationPopUpButton setEnabled:(!isInformationSchemaDb)];
	}

	[tableCommentsTextView setEditable:(!isInformationSchemaDb)];
}

#pragma mark -

/**
 * Release connection.
 */
- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];

	[connection release], connection = nil;
	
	[super dealloc];
}

@end

@implementation SPExtendedTableInfo (PrivateAPI)

/**
 * Format and returns the value within the info dictionary with the associated key. 
 */
- (NSString *)_formatValueWithKey:(NSString *)key inDictionary:(NSDictionary *)infoDict withLabel:(NSString *)label
{
	NSString *value = [infoDict objectForKey:key];
	
	if ([value isKindOfClass:[NSNull class]]) {
		value = @"";
	} 
	else {
		// Format size strings
		if ([key isEqualToString:@"Data_length"]     || 
			[key isEqualToString:@"Max_data_length"] || 
			[key isEqualToString:@"Index_length"]    || 
			[key isEqualToString:@"Data_free"]) {
			
			value = [NSString stringForByteSize:[value longLongValue]];
		}
		// Format date strings to the user's long date format
		else if ([key isEqualToString:@"Create_time"] ||
				 [key isEqualToString:@"Update_time"]) {
			
			// Create date formatter
			NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
			
			[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
			
			[dateFormatter setDateStyle:NSDateFormatterLongStyle];
			[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
			
			value = [dateFormatter stringFromDate:[NSDate dateWithNaturalLanguageString:value]];
		}
		// Format numbers
		else if ([key isEqualToString:@"Rows"] ||
				 [key isEqualToString:@"Avg_row_length"] || 
				 [key isEqualToString:@"Auto_increment"]) {
			
			NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
			
			[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];

			value = [numberFormatter stringFromNumber:[NSNumber numberWithLongLong:[value longLongValue]]];

			// Prefix number of rows with '~' if it is not an accurate count
			if ([key isEqualToString:@"Rows"] && ![[infoDict objectForKey:@"RowsCountAccurate"] boolValue]) {
				value = [@"~" stringByAppendingString:value];
			}
		}
	}
	
	if ([key isEqualToString:@"Auto_increment"]) {
		return ([value length] > 0) ? value : NSLocalizedString(@"Not available", @"not available label");
	}
	else {
		return [NSString stringWithFormat:@"%@: %@", label, ([value length] > 0) ? value : NSLocalizedString(@"Not available", @"not available label")];
	}
}

@end