//
//  WBModelSidebarController.mm
//  MySQLWorkbench
//
//  Created by Alfredo Kojima on 15/Dec/08.
//  Copyright 2008 Sun Microsystems Inc. All rights reserved.
//

#import "sigobjc++.h"
#import "WBModelSidebarController.h"
#import "GRTTreeDataSource.h"
#import "GRTListDataSource.h"
#import "MCanvasViewer.h"
#import "WBObjectDescriptionController.h"
#import "WBObjectPropertiesController.h"
#import "MOutlineView.h"
#import "MTextImageCell.h"
#import "GRTIconCache.h"

#include "workbench/wb_context_ui.h"
#include "workbench/wb_history_tree.h"
#include "wb_layer_tree.h"
#include "wb_model_diagram_form.h"
#include "wb_user_datatypes.h"

#import "MCPPUtilities.h"

#include "grtdb/db_object_helpers.h"


@implementation WBModelSidebarController

- (NSView*)sidebar
{
  return sidebarView;
}



static int zoom_levels[]= {
200,
150,
100,
95,
90,
85,
80,
75,
70,
60,
50,
40,
30,
20,
10
};

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

- (void)resetForNewModel
{
  _wbui->setup_mini_view([navigatorViewer canvas]);
  _wbui->update_mini_view_size(NSWidth([navigatorViewer frame]), NSHeight([navigatorViewer frame]));

  [catalogDS setTreeModel: _wbui->get_catalog_tree()];
  
  [layerDS setTreeModel: _wbui->get_layer_tree()];
  
  // et backend for usertype ds
  [userTypeDS setListModel: _wbui->get_usertypes_tree()];
  
  // set backend model for history DS
  [historyDS setListModel:_wbui->get_history_tree()];  
}

//--------------------------------------------------------------------------------------------------
  
- (void)setupWithWBContextUI:(wb::WBContextUI*)wbui
{
  _wbui= wbui;
 
  [NSBundle loadNibNamed:@"WBModelSidebar" owner:self];
  
  for (int i= 0; i < sizeof(zoom_levels)/sizeof(int); i++)
    [zoomCombo addItemWithObjectValue:[NSNumber numberWithFloat:zoom_levels[i]]];

  [navigatorViewer setupQuartz];
  [navigatorViewer setPostsFrameChangedNotifications:YES];
  [[NSNotificationCenter defaultCenter] addObserver: self
                                           selector: @selector(navigatorFrameChanged:)
                                               name: NSViewFrameDidChangeNotification
                                             object: navigatorViewer];
  [[NSNotificationCenter defaultCenter] addObserver: self
                                           selector: @selector(menuAction:)
                                               name: NSMenuActionNotification
                                             object: nil];
  
  [catalogOutline setDoubleAction:@selector(activateCatalogItem:)];
  [catalogOutline setTarget:self];
  [layerOutline setDoubleAction:@selector(activateLayerItem:)];
  [layerOutline setTarget:self];
  [historyTable setDoubleAction:@selector(activateHistoryItem:)];
  [historyTable setTarget:self];
  
  // set backend model for catalog DS
  [catalogDS setHidesRootNode:YES];
  [catalogDS setDragDelegate:self];
  
  // set backend model for layer DS
  [layerDS setHidesRootNode:YES];
  
  [descriptionController setWBContext:_wbui];
  [mPropertiesController setWBContext:_wbui];
  
  [self refresh];
}


- (void) dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver: self];
  [super dealloc];
}

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

/**
 * Triggered if the underlying outline view was told the backend does not handle the specific
 * menu command (usually the case for pure UI related tasks). Thus handle them here.
 */
- (void) menuAction: (NSNotification*) notification
{
  id sender = [notification object];
  NSString* command = [sender representedObject];
  
  if ([command compare: @"refreshCatalog"] == NSOrderedSame)
    [self refreshCatalogTree];
  else
    if ([command compare: @"copyHistory"] == NSOrderedSame)
      [self copyHistoryToPasteboard];
}

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

- (void) copyHistoryToPasteboard
{
  NSString* text = @"";
  
  bec::ListModel* model = [historyDS listModel];
  std::vector<bec::NodeId> nodes = [historyTable selectedNodeIds];
  if (nodes.size() > 0)
  {
    // Copy only selected items if there are any.
    for (int i = 0; i < nodes.size(); i++)
    {
      std::string label;
      model->get_field(nodes[i], 0, label);
      text = [NSString stringWithFormat: @"%@%@\n", text, [NSString stringWithUTF8String: label.c_str()]];
    }
  }
  else
  {
    // Nothing selected, copy everything.
    for (int i = 0; i < model->count(); i++)
    {
      bec::NodeId node = model->get_node(i);
      
      std::string label;
      model->get_field(node, 0, label);
      text = [NSString stringWithFormat: @"%@%@\n", text, [NSString stringWithUTF8String: label.c_str()]];
    }
  }    
  
  NSPasteboard* pasteboard= [NSPasteboard generalPasteboard];
  [pasteboard declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil];
  [pasteboard setString: text forType: NSStringPboardType];
}

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

- (void)navigatorFrameChanged:(NSNotification*)notif
{
  if (_wbui->get_wb()->get_model_context())
  {
    _wbui->update_mini_view_size(NSWidth([navigatorViewer frame]), NSHeight([navigatorViewer frame]));
  }
  [navigatorViewer setNeedsDisplay:YES];
}


- (void)refresh
{
  [self refreshInfo];
  [self refreshZoom];
  [self refreshCatalogTree];
  [self refreshLayerTree];
  [self refreshUserTypeList];
}

- (IBAction)setZoom:(id)sender
{
  if (sender == zoomSlider || sender == zoomCombo)
  {
    wb::ModelDiagramForm *form= dynamic_cast<wb::ModelDiagramForm*>(_wbui->get_active_main_form());
    
    if (form)
      form->set_zoom([sender floatValue]/100.0);
    
    [self refreshZoom];
  }
  else if (NSMinX([sender frame]) < NSMinX([zoomSlider frame]))
  {
    wb::ModelDiagramForm *form= dynamic_cast<wb::ModelDiagramForm*>(_wbui->get_active_main_form());
    
    if (form)
      form->zoom_out();
    
    [self refreshZoom];
  }
  else if (NSMaxX([sender frame]) > NSMaxX([zoomSlider frame]))
  {
    wb::ModelDiagramForm *form= dynamic_cast<wb::ModelDiagramForm*>(_wbui->get_active_main_form());
    
    if (form)
      form->zoom_in();
    
    [self refreshZoom];
  }
}


- (void)refreshInfo
{
  if (_wbui)
  {
    std::string info= _wbui->get_active_diagram_info();

    [changeButton setEnabled: !info.empty()];
    [diagramSizeText setStringValue: [NSString stringWithCPPString: info]];
  }
  else
  {
    [changeButton setEnabled: NO];
    [diagramSizeText setStringValue: @""];
  }
  [diagramSizeText sizeToFit];
}


- (void)refreshZoom
{
  if (_wbui)
  {
    wb::ModelDiagramForm *form= dynamic_cast<wb::ModelDiagramForm*>(_wbui->get_active_main_form());
    
    [zoomSlider setEnabled:form!=0];
    [zoomCombo setEnabled:form!=0];
    
    if (form)
    {
      [zoomSlider setIntegerValue:form->get_zoom()*100];
      [zoomCombo setIntegerValue:form->get_zoom()*100];
    }
    else
    {
      [zoomSlider setIntegerValue:100];
      [zoomCombo setIntegerValue:100];
    }
  }
  [self refreshInfo];
}


- (void)refreshUserTypeList
{
  if ([userTypeDS listModel])
    [userTypeDS listModel]->refresh();
  [userTypeTable reloadData];
}


- (void)refreshCatalogTree
{
  if (_wbui)
  {
    _wbui->get_catalog_tree()->refresh();
    [catalogOutline reloadData];
  
    [catalogOutline expandItem:[catalogOutline itemAtRow: 0]];
    // expand everything from bottom to top so that only the folders get expanded
    for (int i= [catalogOutline numberOfRows]-1; i >= 1; --i)
    {
      // force expansion of node
      [catalogOutline collapseItem:[catalogOutline itemAtRow:i]];
      [catalogOutline expandItem:[catalogOutline itemAtRow:i]];
    }
  }
}


- (BOOL)dataSource:(id)source
        writeItems:(NSArray*)items
      toPasteboard:(NSPasteboard*)pboard
{
  if (source == catalogDS)
  {
    std::list<db_DatabaseObjectRef> objects;
    std::string path;
    
    for (id item in items)
    {
      bec::NodeId node= [source nodeIdForItem:item];
      grt::ValueRef value(_wbui->get_catalog_tree()->get_node_value(node));

      if (value.is_valid() && db_DatabaseObjectRef::can_wrap(value))
      {
        objects.push_back(db_DatabaseObjectRef::cast_from(value));
        
        path= "/wb/doc/physicalModels/0/catalog/schemata" + _wbui->get_catalog_tree()->get_path_for_node(node, true);
      }
    }

    std::string str= bec::CatalogHelper::dbobject_list_to_dragdata(objects);
    if (!str.empty())
    {
      [pboard declareTypes:[NSArray arrayWithObjects:
                            @"x-mysql-wb/db.DatabaseObject", 
                            NSStringPboardType,
                            nil]
                     owner:nil];

      [pboard setString:[NSString stringWithCString:str.c_str()]
                forType:@"x-mysql-wb/db.DatabaseObject"];
      [pboard setString:[NSString stringWithCString:path.c_str()]
                forType:NSStringPboardType];
      return YES;
    }
  }
  return NO;
}


- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
  if (outlineView == catalogOutline)
  {
    if ([cell isKindOfClass:[MTextImageCell class]])
    {
      bec::NodeId node_id= [catalogDS nodeIdForItem:item];
      bec::TreeModel *tree= [catalogDS treeModel];
      bec::IconId icon_id= tree->get_field_icon(node_id, [[tableColumn identifier] integerValue], bec::Icon16);
      
      NSImage *image= [[GRTIconCache sharedIconCache] imageForIconId:icon_id];
      
      if (icon_id != 0 && !image && tree->is_expandable(node_id))
      {
        image= [[GRTIconCache sharedIconCache] imageForFolder:bec::Icon16];
      }
      
      if (icon_id != 0)
        [cell setImage:image];
      else
        [cell setImage:nil];
      
      std::string tmp;
      tree->get_field(node_id, 1, tmp);
      if (tmp.empty())
        [cell setFont: [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]];
      else
        [cell setFont: [NSFont boldSystemFontOfSize: [NSFont smallSystemFontSize]]];
    }
  }
}


- (void)outlineViewItemWillExpand:(NSNotification *)notification
{
  [[[notification object] dataSource] outlineViewItemWillExpand: notification];
}


- (void)outlineViewItemDidCollapse:(NSNotification *)notification
{
  [[[notification object] dataSource] outlineViewItemDidCollapse: notification];
}



- (void)refreshLayerTree
{
  if (_wbui)
  {
    bec::TreeModel *layerTree= _wbui->get_layer_tree();
    
    layerTree->refresh();
    [layerOutline reloadData];
    
    for (size_t c= layerTree->count(), i= 0; i < c; i++)
    {
      bec::NodeId node= layerTree->get_node(i);
      int islayer;
      
      layerTree->get_field(node, (int)wb::LayerTreeBE::IsLayer, islayer);
      if (islayer)
        [layerOutline expandItem:[layerDS itemForNodeId:node]];
    }
  }
}


- (void)refreshHistory
{
  if (_wbui)
  {
    _wbui->get_history_tree()->refresh();
    [historyTable reloadData];
  }
}


- (void)updateForSelectionChange
{
  if (_wbui)
  {
    [descriptionController updateForForm:_wbui->get_active_form()];
    [mPropertiesController updateForForm:_wbui->get_active_form()];
  }
  [self refreshInfo];
}


- (IBAction)changeDiagramSize:(id)sender
{
  if (_wbui && _wbui->get_active_model_diagram(false).is_valid())
    _wbui->get_command_ui()->activate_command("builtin:diagram_size");
  
  [self refreshInfo];
}


- (IBAction)activateCatalogItem:(id)sender
{
  _wbui->get_catalog_tree()->activate_node([catalogDS nodeIdForItem:[catalogOutline itemAtRow: [catalogOutline clickedRow]]]);
}


- (IBAction)activateLayerItem:(id)sender
{
  _wbui->get_layer_tree()->activate_node([layerDS nodeIdForItem:[layerOutline itemAtRow: [layerOutline clickedRow]]]);
}


- (IBAction)activateHistoryItem:(id)sender
{
  _wbui->get_history_tree()->activate_node(bec::NodeId([historyTable clickedRow]));
}

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

- (NSOutlineView*) catalogOutline
{
  return catalogOutline;
}

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

- (GRTTreeDataSource*) catalogDS
{
  return catalogDS;
}

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

- (NSTableView*) historyTable
{
  return historyTable;
}

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

- (GRTListDataSource*) historyDS
{
  return historyDS;
}

@end
