//
//  MySQLRoutineGroupEditor.mm
//  MySQLWorkbench
//
//  Created by Alfredo Kojima on 23/Feb/09.
//  Copyright 2009 Sun Microsystems Inc. All rights reserved.
//

#import "MySQLRoutineGroupEditor.h"
#import "MCPPUtilities.h"
#include "grtdb/db_object_helpers.h" // get_rdbms_for_db_object()


@implementation DbMysqlRoutineGroupEditor

static void call_refresh(DbMysqlRoutineGroupEditor *self)
{
  [self performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:YES];
}

- (id)initWithModule:(grt::Module*)module GRTManager:(bec::GRTManager*)grtm arguments:(const grt::BaseListRef&)args
{
  self= [super initWithModule:module GRTManager:grtm arguments:args];
  if (self != nil)
  {
    // load GUI. Top level view in the nib is the NSTabView that will be docked to the main window
    if (![[NSBundle bundleForClass:[self class]] loadNibFile:@"MySQLRoutineGroupEditor"
                                           externalNameTable:[NSDictionary dictionaryWithObject:self forKey:NSNibOwner] 
                                                    withZone:nil])
      NSLog(@"Could not load MySQLRoutineGroupEditor.xib");
    
    [routineTable registerForDraggedTypes: 
      [NSArray arrayWithObject: @"x-mysql-wb/db.DatabaseObject"]];
    
    // take the minimum size of the view from the initial size in the nib.
    // Therefore the nib should be designed as small as possible
    // note: the honouring of the min size is not yet implemented
    [self setMinimumSize: [tabView frame].size];

    [self reinitWithArguments: args];
  }
  return self;
}


- (void)reinitWithArguments:(const grt::BaseListRef&)args
{
  delete mBackEnd;
  mBackEnd= new MySQLRoutineGroupEditorBE(_grtm, db_mysql_RoutineGroupRef::cast_from(args[0]), get_rdbms_for_db_object(args[0]));
  
  // register a callback that will make [self refresh] get called
  // whenever the backend thinks its needed to refresh the UI from the backend data (ie, the
  // edited object was changed from somewhere else in the application)
  mBackEnd->set_refresh_ui_slot(sigc::bind(sigc::ptr_fun(call_refresh), self));
  
  [mRoutineArray release];
  mRoutineArray= [[NSMutableArray array] retain];
  
  mNeedsInit= YES;
  
  [self setupEditor];
  
  // update the UI
  [self refresh];  
}


- (void) dealloc
{
  [mRoutineArray release];
  delete mBackEnd;
  [super dealloc];
}


/** Fetches object info from the backend and update the UI
 */
- (void) refresh
{
  if (mBackEnd)
  {
    std::string code;
    
    [nameText setStringValue: [NSString stringWithCPPString: mBackEnd->get_name()]];
    [self updateTitle: [self title]];
    
    [commentText setString: [NSString stringWithCPPString: mBackEnd->get_comment()]];
    
    code= mBackEnd->get_routines_sql();
    
    [mRoutineArray removeAllObjects];
    
    std::vector<std::string> names(mBackEnd->get_routines_names());
    for (std::vector<std::string>::const_iterator iter= names.begin(); iter != names.end(); ++iter)
    {
      [mRoutineArray addObject: [NSString stringWithCPPString: *iter]];
    }
    [routineTable reloadData];
    
    if (mNeedsInit)
      [codeText setString: [NSString stringWithCPPString:code]];
    mNeedsInit= NO;
  }
}


- (id)identifier
{
  // an identifier for this editor (just take the object id)
  return [NSString stringWithCPPString: mBackEnd->get_object().id()];
}


- (NSString*)title
{
  // the title for the editor
  return [NSString stringWithCPPString: strfmt("%s - Group", mBackEnd->get_name().c_str())];
}


- (NSView*)dockableView
{
  // the view to be docked to the main window
  return tabView;
}


- (NSDragOperation)tableView:(NSTableView *)aTableView 
                validateDrop:(id < NSDraggingInfo >)info
                 proposedRow:(NSInteger)row
       proposedDropOperation:(NSTableViewDropOperation)operation
{
  id data= [[info draggingPasteboard] stringForType: @"x-mysql-wb/db.DatabaseObject"];
  if (data)
  {
    std::list<db_DatabaseObjectRef> objects;
    std::string text= [data UTF8String];
    
    objects= bec::CatalogHelper::dragdata_to_dbobject_list(mBackEnd->get_catalog(), text);

    for (std::list<db_DatabaseObjectRef>::const_iterator obj= objects.begin(); 
         obj != objects.end(); ++obj)
    {
      if (!obj->is_instance<db_mysql_Routine>())
        return NSDragOperationNone;
    }
    if (!objects.empty())
    {
      [aTableView setDropRow:-1 dropOperation: NSTableViewDropOn];
      return NSDragOperationCopy;
    }
  }
  return NSDragOperationNone;
}


- (BOOL)tableView:(NSTableView *)aTableView 
       acceptDrop:(id < NSDraggingInfo >)info
              row:(NSInteger)row
    dropOperation:(NSTableViewDropOperation)operation
{
  id data= [[info draggingPasteboard] stringForType: @"x-mysql-wb/db.DatabaseObject"];
  if (data)
  {
    std::list<db_DatabaseObjectRef> objects;
    std::string text= [data UTF8String];
    
    objects= bec::CatalogHelper::dragdata_to_dbobject_list(mBackEnd->get_catalog(), text);
    
    for (std::list<db_DatabaseObjectRef>::const_iterator obj= objects.begin(); 
         obj != objects.end(); ++obj)
    {
      if (!obj->is_instance<db_mysql_Routine>())
        return NSDragOperationNone;

      db_mysql_RoutineRef routine = db_mysql_RoutineRef::cast_from(*obj);
      if ( routine.is_valid() )
      {
        mBackEnd->append_routine_with_id(routine.id());
      }
    }
    if (!objects.empty())
    {
      mNeedsInit = YES;
      [self refresh];
      return YES;
    }
  }    
  return NO;
}


- (void) updateBackend
{
  mBackEnd->set_routines_sql([[codeText string] UTF8String]);
}


- (BOOL)matchesIdentifierForClosingEditor:(NSString*)identifier
{
  return mBackEnd->should_close_on_delete_of([identifier UTF8String]);
}


- (void)controlTextDidEndEditing:(NSNotification *)aNotification
{
  if ([aNotification object] == nameText)
  {
    // set name of the schema
    mBackEnd->set_name([[nameText stringValue] UTF8String]);
    [self updateTitle: [self title]];
  }
}


- (void)textDidEndEditing:(NSNotification *)aNotification
{
  if ([aNotification object] == codeText)
  {    
    [self updateBackend];
  }
  else if ([aNotification object] == commentText)
  {
    mBackEnd->set_comment([[commentText string] UTF8String]);
  }
}


- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
  return [mRoutineArray count];
}


- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
  return [mRoutineArray objectAtIndex: rowIndex];
}

/**
 * Callback from the backend telling us about syntax errors it founds.
 */
static int process_syntax_error(const int line, const int err_tok_line_pos, const int err_tok_len,
                                const std::string& err_msg, DbMysqlRoutineGroupEditor* self)
{
  // If this is the first new error then reset all error markers.
  // Don't do this in advance in checkSyntax, otherwise we get some ugly flickering.
  if (self->mErrorCount == 0)
    [self resetSyntaxErrors];
  
  Scintilla::ScintillaCocoa *backend= self->codeText.backend;
  int line_start_pos= backend->WndProc(SCI_POSITIONFROMLINE, line - 1, 0);
  
  backend->WndProc(SCI_SETINDICATORCURRENT, 0, 0);
  backend->WndProc(SCI_INDICATORFILLRANGE, line_start_pos + err_tok_line_pos, err_tok_len);
  
  backend->WndProc(SCI_MARKERADD, line - 1, 0);
  
  ++self->mErrorCount;
  
  return 0;
}

//--------------------------------------------------------------------------------------------------

/**
 * Prepares the code editor and the backend to have proper syntax highlighting, error parsing etc.
 */
- (void) setupEditor
{
  [[NSNotificationCenter defaultCenter] removeObserver: self
                                                  name: nil
                                                object: codeText];
  
  [[NSNotificationCenter defaultCenter] addObserver: self
                                           selector: @selector(textDidChange:)
                                               name: NSTextDidChangeNotification
                                             object: codeText];
  [[NSNotificationCenter defaultCenter] addObserver: self
                                           selector: @selector(textDidEndEditing:)
                                               name: NSTextDidEndEditingNotification
                                             object: codeText];
  [WBPluginEditorBase setupCodeEditor: codeText backend: mBackEnd->get_sql_editor() withStatus: YES];
  [codeText setStatusText: @"No errors found"];
  
  // Connect the parser with our callback to get notifications about syntax errors.
  mBackEnd->get_sql_editor()->sql_parser_err_cb(sigc::bind(sigc::ptr_fun(process_syntax_error), self));
}

//--------------------------------------------------------------------------------------------------

/**
 * Called when text was added or removed in the editor.
 */
- (void) textDidChange: (NSNotification*) aNotification
{
  if ([aNotification object] == commentText)
  {
    // Set comment for the view.
    mBackEnd->set_comment([[commentText string] UTF8String]);
  }
  else
  {
    // Stop the timer in case it is about to trigger.
    [mSyntaxCheckTimer invalidate];
    
    // Set up a new timer.
    mSyntaxCheckTimer= [NSTimer scheduledTimerWithTimeInterval: 0.5
                                                        target: self
                                                      selector: @selector(checkSyntax:)
                                                      userInfo: nil
                                                       repeats: NO];
  }
};

//--------------------------------------------------------------------------------------------------

/**
 * Remove all markers we set for previous syntax errors.
 */
- (void) resetSyntaxErrors
{
  [codeText setStatusText: @"No errors found"];
  int length= codeText.backend->WndProc(SCI_GETLENGTH, 0, 0);
  
  [codeText setGeneralProperty: SCI_SETINDICATORCURRENT parameter: 0 value: 0];
  [codeText setGeneralProperty: SCI_INDICATORCLEARRANGE parameter: 0 value: length];
  
  [codeText setGeneralProperty: SCI_MARKERDELETEALL parameter: -1 value: 0];
}

//--------------------------------------------------------------------------------------------------

/**
 * Triggered once a change in the editor content happend and there was a pause of at least 500 ms.
 */
- (void) checkSyntax:(NSTimer*) timer
{
  mSyntaxCheckTimer = nil;
  NSString *text;
  
  text= [codeText string];
  if (text)
  {
    // This is kinda suboptimal as it creates an undo record, even though we only want error checking
    // here. However, without that routine renames are not recognized. Needs investigation.
    [self updateBackend];
    
    [codeText setStatusText: @"Parsing SQL ..."];
    mBackEnd->get_sql_editor()->sql([text UTF8String]);
    
    mErrorCount = 0;
    mBackEnd->get_sql_editor()->check_sql();
    
    // If there was no syntax error then reset any remaining error markers now.
    if (mErrorCount == 0)
      [self resetSyntaxErrors];
    else
      [codeText setStatusText: [NSString stringWithFormat: @"%d error(s) found.", mErrorCount]];
  }
  else
    [self resetSyntaxErrors];
}

//--------------------------------------------------------------------------------------------------

/**
 * Called when clicking the [-] button.
 */
- (IBAction) removeItem: (id) sender
{
  NSIndexSet* selectedRows = [routineTable selectedRowIndexes];
  NSUInteger index = [selectedRows lastIndex];
  while (index != NSNotFound)
  {
    mBackEnd->remove_routine_by_index(index);
    index = [selectedRows indexLessThanIndex: index];
  }
  mNeedsInit = YES;
  [self refresh];
}

@end
