#import <cassert>
#import <cmath>

#import <QuartzCore/QuartzCore.h>

#import "FileContentViewController.h"
#import "SlideshowViewController.h"
#import "ObjectViewController.h"
#import "FileContainerElement.h"
#import "TransparentToolbar.h"
#import "ObjectShortcutView.h"
#import "SpotObjectView.h"
#import "Shortcuts.h"
#import "Constants.h"

//C++ imports.
#import "IOSPad.h"

#import "FileUtils.h"

@implementation FileContentViewController {
   ROOT::iOS::Browser::FileContainer *fileContainer;
   
   __weak IBOutlet UIScrollView *scrollView;

   NSMutableArray *objectShortcuts;
   
   UISearchBar *searchBar;
   UIPopoverController *searchPopover;
   SearchViewController *searchController;
   
   UIBarButtonItem *slideShowBtn;
   
   BOOL animateDirAfterLoad;
   BOOL animateObjAfterLoad;

   unsigned spotElement;
   
   BOOL viewDidAppear;
   
   BOOL animating;
}

@synthesize fileContainer;


//____________________________________________________________________________________________________
- (void) initToolbarItems
{
   UIToolbar *toolbar = [[TransparentToolbar alloc] initWithFrame : CGRectMake(0.f, 0.f, 250.f, 44.f)];
   toolbar.barStyle = UIBarStyleBlackTranslucent;

   NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity : 2];
   
   searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.f, 0.f, 150.f, 44.f)];
   searchBar.delegate = self;

   UIBarButtonItem *searchItem = [[UIBarButtonItem alloc] initWithCustomView : searchBar];
   [items addObject : searchItem];

   slideShowBtn = [[UIBarButtonItem alloc] initWithTitle : @"Slide show" style : UIBarButtonItemStyleBordered target : self action : @selector(startSlideshow)];
   [items addObject : slideShowBtn];
   
   [toolbar setItems : items animated : NO];
   
   UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithCustomView : toolbar];
   rightItem.style = UIBarButtonItemStylePlain;
   self.navigationItem.rightBarButtonItem = rightItem;
}

//____________________________________________________________________________________________________
- (instancetype) initWithCoder : (NSCoder *) aDecoder
{
   if (self = [super initWithCoder : aDecoder]) {
      //
      viewDidAppear = NO;
      animating = NO;
   }
   
   return self;
}

#pragma mark - View lifecycle

//____________________________________________________________________________________________________
- (void) viewWillAppear : (BOOL) animated
{
   [super viewWillAppear : animated];
   [self correctFramesForOrientation : self.interfaceOrientation];
}

//____________________________________________________________________________________________________
- (void) viewDidLoad
{
   [super viewDidLoad];
   //
   [self initToolbarItems];
   searchController = [[SearchViewController alloc] initWithStyle : UITableViewStylePlain];
   searchController.delegate = self;
   //
   assert(fileContainer != nil && "viewDidLoad, fileContainer is nil");
   //Create object shortcuts.
   self.navigationItem.title = [NSString stringWithFormat : @"Contents of %s", fileContainer->GetFileName()];
   slideShowBtn.enabled = fileContainer->GetNumberOfObjects() > 1 ? YES : NO;
   [self addObjectsIntoScrollview];
}

//____________________________________________________________________________________________________
- (void) viewDidAppear : (BOOL)animated
{
   [super viewDidAppear : animated];
   
   if (animateDirAfterLoad) {
      [self highlightDirectory : spotElement];
      animateDirAfterLoad = NO;
   } else if (animateObjAfterLoad) {
      [self highlightObject : spotElement];
      animateObjAfterLoad = NO;
   }
}

//____________________________________________________________________________________________________
- (void) viewDidLayoutSubviews
{
   [self correctFramesForOrientation : self.interfaceOrientation];
}

#pragma mark - Views' geometry + interface orientation.

//____________________________________________________________________________________________________
- (void) correctFramesForOrientation : (UIInterfaceOrientation) orientation
{
#pragma unused(orientation)

   //It's a legacy code - in the past I was resetting view's geometry manually.
   //Now it's done with automatic layout + I'm setting shortcuts myself.

   using ROOT::iOS::Browser::PlaceShortcutsInScrollView;
   
   if ([[scrollView subviews] count]) {
      PlaceShortcutsInScrollView(objectShortcuts, scrollView,
                                 CGSizeMake([ObjectShortcutView iconWidth], [ObjectShortcutView iconHeight] + [ObjectShortcutView textHeight]),
                                 100.f);
   }
}


//____________________________________________________________________________________________________
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation
{
#pragma unused(interfaceOrientation)
	return YES;
}

//____________________________________________________________________________________________________
- (void) willAnimateRotationToInterfaceOrientation : (UIInterfaceOrientation)interfaceOrientation duration : (NSTimeInterval) duration
{
#pragma unused(duration)
   [self correctFramesForOrientation : interfaceOrientation];
}

//____________________________________________________________________________________________________
- (void) didRotateFromInterfaceOrientation : (UIInterfaceOrientation) fromInterfaceOrientation
{
#pragma unused(fromInterfaceOrientation)
   //Bring back the popover after rotating.
   if (searchPopover) {
      [searchPopover presentPopoverFromRect : searchBar.bounds inView : searchBar
      permittedArrowDirections : UIPopoverArrowDirectionAny animated : NO];
   }
}

#pragma mark - objects and folders (shortcut views).

//____________________________________________________________________________________________________
- (void) clearScrollview
{
   NSArray * const viewsToRemove = [scrollView subviews];
   for (UIView *v in viewsToRemove)
      [v removeFromSuperview];
}

//____________________________________________________________________________________________________
- (void) addShortcutForObjectAtIndex : (unsigned) objIndex
{
   const CGRect rect = CGRectMake(0.f, 0.f, [ObjectShortcutView iconWidth], [ObjectShortcutView iconHeight]);
   UIGraphicsBeginImageContext(rect.size);
   CGContextRef ctx = UIGraphicsGetCurrentContext();
   if (!ctx) {
      UIGraphicsEndImageContext();
      return;
   }
      
   //Now draw into this context.
   CGContextTranslateCTM(ctx, 0.f, rect.size.height);
   CGContextScaleCTM(ctx, 1.f, -1.f);
      
   //Fill bitmap with white first.
   CGContextSetRGBFillColor(ctx, 1.f, 1.f, 1.f, 1.f);
   CGContextFillRect(ctx, rect);
   //Set context and paint pad's contents.
   ROOT::iOS::Pad *pad = fileContainer->GetPadAttached(objIndex);
   pad->cd();
   pad->SetViewWH(rect.size.width, rect.size.height);
   pad->SetContext(ctx);
   pad->PaintThumbnail();
   
   UIImage *thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();//autoreleased UIImage.
   UIGraphicsEndImageContext();
       
   ObjectShortcutView * const shortcut = [[ObjectShortcutView alloc] initWithFrame : [ObjectShortcutView defaultRect]
                                          controller : self forObjectAtIndex : objIndex withThumbnail : thumbnailImage];
   shortcut.layer.shadowColor = [UIColor blackColor].CGColor;
   shortcut.layer.shadowOffset = CGSizeMake(20.f, 20.f);
   shortcut.layer.shadowOpacity = 0.3f;

   [scrollView addSubview : shortcut];
   [objectShortcuts addObject : shortcut];

   UIBezierPath *path = [UIBezierPath bezierPathWithRect : rect];
   shortcut.layer.shadowPath = path.CGPath;
}

//____________________________________________________________________________________________________
- (void) addShortcutForFolderAtIndex : (unsigned) index
{
   ObjectShortcutView * const shortcut = [[ObjectShortcutView alloc] initWithFrame : [ObjectShortcutView defaultRect]
                                          controller : self forFolderAtIndex : index];
   [scrollView addSubview : shortcut];
   [objectShortcuts addObject : shortcut];
}

//____________________________________________________________________________________________________
- (void) addObjectsIntoScrollview
{
   using namespace ROOT::iOS::Browser;

   [self clearScrollview];

   objectShortcuts = [[NSMutableArray alloc] init];

   //Add directories first.
   for (FileContainer::size_type i = 0; i < fileContainer->GetNumberOfDirectories(); ++i)
      [self addShortcutForFolderAtIndex : i];
   //Now add objects.
   for (FileContainer::size_type i = 0; i < fileContainer->GetNumberOfObjects(); ++i)
      [self addShortcutForObjectAtIndex : i];
}

//____________________________________________________________________________________________________
- (void) activateForFile : (ROOT::iOS::Browser::FileContainer *) container
{
   assert(container != nullptr && "activateForFile:, parameter 'container' is null");

   fileContainer = container;
}

//____________________________________________________________________________________________________
- (void) startSlideshow
{
   if (animating)
      return;

   assert(self.storyboard != nil && "startSlideshow, self.storyboard is nil");
   assert(fileContainer != nullptr && "startSlideshow, fileContainer is null");

   SlideshowViewController * const slideshowController = [self.storyboard instantiateViewControllerWithIdentifier:ROOT::iOS::Browser::SlideshowViewControllerID];
   [slideshowController setFileContainer : fileContainer];
   [self.navigationController pushViewController : slideshowController animated : YES];
}

//____________________________________________________________________________________________________
- (void) doTest
{
   const unsigned testIndex = 1 + rand() % (fileContainer->GetNumberOfObjects() - 1);
   ObjectViewController *objectController = [[ObjectViewController alloc] initWithNibName : @"ROOTObjectController" bundle : nil];
   [objectController setNavigationForObjectWithIndex : testIndex fromContainer : fileContainer];
   [self.navigationController pushViewController : objectController animated : YES];
}

//____________________________________________________________________________________________________
- (void) selectObjectFromFile : (ObjectShortcutView *) shortcut
{
   if (animating)
      return;

   assert(shortcut != nil && "selectObjectFromFile:, parameter shortcut is nil");
   assert(fileContainer != nullptr && "selectObjectFromFile:, fileContainer is null");
   assert(self.storyboard != nil && "selectObjectFromFile:, self.storyboard is nil");

   if (shortcut.isDirectory) {
      //Create another FileContentController and push it on stack.
      UIViewController * const c = (UIViewController *)[self.storyboard instantiateViewControllerWithIdentifier : ROOT::iOS::Browser::FileContentViewControllerID];
      assert([c isKindOfClass : [FileContentViewController class]] && "file content controller has a wrong type");
      FileContentViewController * const contentController = (FileContentViewController *)c;
      [contentController activateForFile : fileContainer->GetDirectory(shortcut.objectIndex)];
      [self.navigationController pushViewController : contentController animated : YES];
   } else {
      UIViewController * const c = (UIViewController *)[self.storyboard instantiateViewControllerWithIdentifier : ROOT::iOS::Browser::ObjectViewControllerID];
      assert([c isKindOfClass : [ObjectViewController class]] &&
             "object view controller has a wrong type");
      ObjectViewController * const objectController = (ObjectViewController *)c;
      [objectController setNavigationForObjectWithIndex : shortcut.objectIndex fromContainer : fileContainer];
      [self.navigationController pushViewController : objectController animated : YES];
   }
}

#pragma mark - Search delegate.

//____________________________________________________________________________________________________
- (void) searchBarTextDidBeginEditing : (UISearchBar *) aSearchBar
{
#pragma unused(aSearchBar)

   typedef ROOT::iOS::Browser::FileContainer::size_type size_type;

   if (auto nEntities = fileContainer->GetNumberOfDescriptors()) {
      if (!searchPopover) {         
         UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController : searchController];
         searchPopover = [[UIPopoverController alloc] initWithContentViewController : navController];
         searchPopover.delegate = self;
         searchPopover.passthroughViews = [NSArray arrayWithObject : searchBar];
      }
      
      NSMutableArray *keys = [[NSMutableArray alloc] init];
      for (size_type i = 0; i < nEntities; ++i) {
         const auto &descriptor = fileContainer->GetElementDescriptor(i);
         NSString *formatString = descriptor.fIsDir ? @"%s (directory)" : @"%s";
         FileContainerElement *newKey = [[FileContainerElement alloc] init];
         newKey.elementName = [NSString stringWithFormat : formatString, descriptor.fName.c_str()];
         newKey.elementIndex = i;
         [keys addObject : newKey];
      }
      
      searchController.keys = keys;
      [searchPopover presentPopoverFromRect : [searchBar bounds] inView : searchBar permittedArrowDirections : UIPopoverArrowDirectionAny animated : YES];
   }
}

//____________________________________________________________________________________________________
- (void) searchBarTextDidEndEditing : (UISearchBar *) aSearchBar
{
   if (searchPopover) {
      [searchPopover dismissPopoverAnimated : YES];
      searchPopover = nil;
   }  

   [aSearchBar resignFirstResponder];
}

//____________________________________________________________________________________________________
- (void) searchBar : (UISearchBar *) searchBar textDidChange : (NSString *) searchText
{
#pragma unused(searchBar)
   // When the search string changes, filter the recents list accordingly.
   [searchController filterResultsUsingString : searchText];
}

//____________________________________________________________________________________________________
- (void) searchBarSearchButtonClicked : (UISearchBar *) aSearchBar
{
#pragma unused(aSearchBar)

   [searchPopover dismissPopoverAnimated : YES];
   [searchBar resignFirstResponder];
}

#pragma mark - Popover controller delegate.

//____________________________________________________________________________________________________
- (void) popoverControllerDidDismissPopover : (UIPopoverController *) popoverController
{
#pragma unused(popoverController)
   [searchBar resignFirstResponder];
}

#pragma mark - Search delegate.

//____________________________________________________________________________________________________
- (void) searchController : (SearchViewController *) controller didSelectKey : (FileContainerElement *) key
{
#pragma unused(controller)

   assert(key != nil && "searcheController:didSelectKey:, parameter 'key' is nil");
   assert(key.elementIndex < fileContainer->GetNumberOfDescriptors() &&
          "searcheController:didSelectKey:, key.elementIndex is out of bounds");

   [searchPopover dismissPopoverAnimated : YES];
   searchPopover = nil;
   [searchBar resignFirstResponder];
   
   const auto &descriptor = fileContainer->GetElementDescriptor(key.elementIndex);
   if (descriptor.fOwner == fileContainer) {
      descriptor.fIsDir ? [self highlightDirectory : descriptor.fIndex] : [self highlightObject : descriptor.fIndex];
   } else {
      //Create another FileContentController and push it on stack.
      assert(self.storyboard != nil && "searcheController:didSelectKey:, self.storyboard is nil");
      UIViewController * const c = (UIViewController *)[self.storyboard instantiateViewControllerWithIdentifier : ROOT::iOS::Browser::FileContentViewControllerID];
      assert([c isKindOfClass : [FileContentViewController class]] &&
             "searcheController:didSelectKey, file content controller has a wrong type");
      FileContentViewController * const contentController = (FileContentViewController *)c;
      [contentController activateForFile : descriptor.fOwner];

      if (descriptor.fIsDir)
         contentController->animateDirAfterLoad = YES;
      else
         contentController->animateObjAfterLoad = YES;
      
      contentController->spotElement = descriptor.fIndex;

      [self.navigationController pushViewController : contentController animated : YES];
   }
}

#pragma mark - adjust file container to show search result

//____________________________________________________________________________________________________
- (void) animateShortcut : (ObjectShortcutView *) sh
{
   assert(sh != nil && "animateShortcut:, parameter 'sh' is nil");
   
   const CGRect oldFrame = sh.frame;
   const CGAffineTransform originalTransform = sh.transform;
   const CGAffineTransform newTransform = CGAffineTransformScale(originalTransform, 1.2f, 1.2f);

   sh.transform = newTransform;
   sh.spot.alpha = 0.8f;
   
   animating = YES;

   [UIView beginAnimations : @"show_object" context : nil];
   [UIView setAnimationDuration : 0.5f];
   [UIView setAnimationCurve : UIViewAnimationCurveLinear];
   [UIView setAnimationTransition : UIViewAnimationTransitionNone forView : sh cache : YES];
   [UIView setAnimationDelegate : self];
   [UIView setAnimationDidStopSelector : @selector(animationDidStop:finished:context:)];
   sh.transform = originalTransform;
   sh.spot.alpha = 0.f;
   sh.frame = oldFrame;
   [UIView commitAnimations];
}

//____________________________________________________________________________________________________
- (void) animationDidStop : (NSString *) animationID finished : (NSNumber *) finished context : (void *) context
{
#pragma unused(animationID, context)
   if ([finished boolValue])
      animating = NO;
}

//____________________________________________________________________________________________________
- (void) highlightDirectory : (unsigned) tag
{
   if (animating)
      return;

   for (ObjectShortcutView *sh in objectShortcuts) {
      if (sh.objectIndex == tag && sh.isDirectory) {
         const CGRect thumbFrame = sh.frame;
         const CGRect scrollBounds = scrollView.bounds;
         if (CGRectGetMaxY(thumbFrame) > CGRectGetMaxY(scrollBounds)) {
            //We have to scroll view to show object's or directory's shortcut.
            //Find new Y for bounds.
            const CGFloat newY = CGRectGetMaxY(thumbFrame) - scrollBounds.size.height;
            CGRect newBounds = scrollBounds;
            newBounds.origin.y = newY;
            [scrollView scrollRectToVisible : newBounds animated : YES];
         }
         
         [self animateShortcut : sh];

         break;
      }
   }
}

//____________________________________________________________________________________________________
- (void) highlightObject : (unsigned) tag
{
   if (animating)
      return;

   for (ObjectShortcutView *sh in objectShortcuts) {
      if (sh.objectIndex == tag && !sh.isDirectory) {
         CGRect thumbFrame = sh.frame;
         const CGRect scrollBounds = scrollView.bounds;
         if (CGRectGetMaxY(thumbFrame) > CGRectGetMaxY(scrollBounds)) {
            //We have to scroll view to show object's or directory's shortcut.
            //Find new Y for bounds.
            const CGFloat newY = CGRectGetMaxY(thumbFrame) - scrollBounds.size.height;
            CGRect newBounds = scrollBounds;
            newBounds.origin.y = newY;
            [scrollView scrollRectToVisible : newBounds animated : YES];
         }
         
         [self animateShortcut : sh];

         break;
      }
   }
}

@end
