#import <cstddef>
#import <cstring>
#import <vector>

#import "ObjectViewController.h"
#import "PadSelectionView.h"
#import "Constants.h"
#import "PadView.h"

#import "TAxis.h"

//C++ code (ROOT's ios module)
#import "IOSPad.h"

const CGFloat tapInterval = 0.15f;

@implementation PadView {
   __weak ObjectViewController *controller;

   ROOT::iOS::Pad *pad;

   BOOL panActive;
   
   CGPoint tapPt;
   BOOL processSecondTap;
   
   BOOL isMutable;
}

@synthesize selectionView;
@synthesize zoomed;

//____________________________________________________________________________________________________
- (instancetype) initWithFrame : (CGRect) frame controller : (ObjectViewController *) c forPad : (ROOT::iOS::Pad*) pd
{
   assert(c != nil && "initWithFrame:forPad:, parameter 'c' is nil");
   assert(pd != nullptr && "initWithFrame:forPad:, parameter 'pd' is null");

   if (self = [super initWithFrame : frame]) {
      controller = c;
      pad = pd;
      
      isMutable = YES;
      
      frame.origin = CGPointZero;
      selectionView = [[PadSelectionView alloc] initWithFrame : frame withPad : pad];
      selectionView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin |
                                       UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
      selectionView.hidden = YES;
      [self addSubview : selectionView];
   }

   return self;
}

//____________________________________________________________________________________________________
- (instancetype) initImmutableViewWithFrame : (CGRect) frame
{
   if (self = [super initWithFrame : frame]) {
      controller = nil;
      pad = nullptr;
      selectionView = nil;

      isMutable = NO;
      
      self.multipleTouchEnabled = NO;
   }
   
   return self;
}

//____________________________________________________________________________________________________
- (void) setPad : (ROOT::iOS::Pad *) newPad
{
   assert(newPad != nullptr && "setPad:, parameter 'newPad' is null");

   pad = newPad;
   if (isMutable)
      [selectionView setPad : newPad];
}

//____________________________________________________________________________________________________
- (void) drawRect : (CGRect) rect
{
   // Drawing code
   if (!pad)//assert instead???
      return;

   CGContextRef ctx = UIGraphicsGetCurrentContext();
   CGContextClearRect(ctx, rect);

   pad->SetViewWH(rect.size.width, rect.size.height);
   CGContextTranslateCTM(ctx, 0.f, rect.size.height);
   CGContextScaleCTM(ctx, 1.f, -1.f);

   pad->cd();
   pad->SetContext(ctx);
   pad->Paint();
   
   if (isMutable && !selectionView.hidden)
      [selectionView setNeedsDisplay];
}

//____________________________________________________________________________________________________
- (void) clearPad
{
   assert(pad != nullptr && "clearPad, pad is null");
   pad->Clear();
}

//____________________________________________________________________________________________________
- (void) addPanRecognizer
{
   panActive = YES;
}

//____________________________________________________________________________________________________
- (void) removePanRecognizer
{
   panActive = NO;
}

#pragma mark - Picking/gesture handling and related stuff here.

//____________________________________________________________________________________________________
- (UIImage *) createImageForPicking
{
   assert(pad != nullptr && "createImageForPicking, pad is null");

   if (!isMutable)
      return nil;

   using namespace ROOT::iOS::Browser;
   const CGRect rect = CGRectMake(0.f, 0.f, padW, padH);
   //Create bitmap context.
   UIGraphicsBeginImageContext(rect.size);
   CGContextRef ctx = UIGraphicsGetCurrentContext();

   //Now draw into this context.
   CGContextTranslateCTM(ctx, 0.f, rect.size.height);
   CGContextScaleCTM(ctx, 1.f, -1.f);
      
   //Disable anti-aliasing, to avoid "non-clear" colors.
   CGContextSetAllowsAntialiasing(ctx, 0);
   //Fill bitmap with black (nothing under cursor).
   CGContextSetRGBFillColor(ctx, 0.f, 0.f, 0.f, 1.f);
   CGContextFillRect(ctx, rect);
   //Set context and paint pad's contents
   //with special colors (color == object's identity)
   pad->SetViewWH(rect.size.width, rect.size.height);

   pad->cd();

   pad->SetContext(ctx);
   pad->PaintForSelection();
   
   UIImage * const uiImageForPicking = UIGraphicsGetImageFromCurrentImageContext();//autoreleased UIImage.
   
   UIGraphicsEndImageContext();
   
   return uiImageForPicking;
} 

//____________________________________________________________________________________________________
- (BOOL) fillPickingBufferFromImage : (UIImage *) image
{
   assert(image != nil && "fillPickingBufferFromImage:, parameter 'image' is nil");
   assert(pad != nullptr && "fillPickingBufferFromImage:, pad is null");

   if (!isMutable)
      return NO;

   CGImageRef cgImage = image.CGImage;

   const size_t pixelsW = CGImageGetWidth(cgImage);
   const size_t pixelsH = CGImageGetHeight(cgImage);

   //Declare the number of bytes per row. Each pixel in the bitmap
   //is represented by 4 bytes; 8 bits each of red, green, blue, and
   //alpha.
   const int bitmapBytesPerRow = pixelsW * 4;
   const int bitmapByteCount = bitmapBytesPerRow * pixelsH;

   //Use the generic RGB color space.
   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
   if (!colorSpace) {
      //Log error: color space allocation failed.
      return NO;
   }

   try {
      std::vector<unsigned char> buffer(bitmapByteCount);
      // Create the bitmap context. We want pre-multiplied ARGB, 8-bits
      // per component. Regardless of what the source image format is 
      // (CMYK, Grayscale, and so on) it will be converted over to the format
      // specified here by CGBitmapContextCreate.
      CGContextRef ctx = CGBitmapContextCreate(&buffer[0], pixelsW, pixelsH, 8, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst);
      CGColorSpaceRelease(colorSpace);

      if (!ctx)
         return NO;

      const CGRect rect = CGRectMake(0.f, 0.f, pixelsW, pixelsH);
      //Draw the image to the bitmap context. Once we draw, the memory 
      //allocated for the context for rendering will then contain the 
      //raw image data in the specified color space.
      
      CGContextSetAllowsAntialiasing(ctx, 0);//Check, if I need this for a bitmap.
      CGContextDrawImage(ctx, rect, cgImage);

      pad->SetSelectionBuffer(pixelsW, pixelsH, &buffer[0]);
      // When finished, release the context
      CGContextRelease(ctx);

      return YES;
   } catch (const std::bad_alloc &e) {
      CGColorSpaceRelease(colorSpace);
   }

   return NO;
}

//____________________________________________________________________________________________________
- (BOOL) initPadPicking
{
   if (!isMutable)
      return NO;

   UIImage * const image = [self createImageForPicking];
   if (!image)
      return NO;

   return [self fillPickingBufferFromImage : image];
}

//____________________________________________________________________________________________________
- (CGPoint) scaledPoint : (CGPoint) pt
{
   const CGFloat scale = ROOT::iOS::Browser::padW / self.frame.size.width;
   return CGPointMake(pt.x * scale, pt.y * scale);
}

//____________________________________________________________________________________________________
- (BOOL) pointOnSelectedObject : (CGPoint) pt
{
   //check if there is any object in pt.
   assert(pad != nullptr && "pointOnSelectedObject:, pad is null");
   
   if (!isMutable)
      return NO;

   if (!pad->SelectionIsValid() && ![self initPadPicking])
      return NO;

   const CGPoint newPt = [self scaledPoint : pt];
   return pad->GetSelected() == pad->ObjectInPoint(newPt.x, newPt.y);
}

//Touch events processing.

//____________________________________________________________________________________________________
- (void) handleSingleTap
{
   //Make a selection, fill the editor, disable double tap.
   assert(pad != nullptr && "handleSingleTap, pad is null");
   
   if (!isMutable)
      return;

   if (!pad->SelectionIsValid() && ![self initPadPicking])
      return;

   const CGPoint scaledTapPt = [self scaledPoint : tapPt];
   pad->Pick(scaledTapPt.x, scaledTapPt.y);
   //Tell controller that selection has probably changed.
   [controller objectWasSelected : pad->GetSelected()];
   
   //There were no second tap withing tapInterval, ignore the subsequent tap.
   processSecondTap = NO;
}

//____________________________________________________________________________________________________
- (void) touchesBegan : (NSSet *) touches withEvent : (UIEvent *) event
{
#pragma unused(event)

   assert(touches != nil && "touchesBegan:withEvent:, parameter 'touches' is nil");

   if (!isMutable)
      return;

   UITouch * const touch = [touches anyObject];
   if (touch.tapCount == 1) {
      //The first tap - interaction has started.
      tapPt = [touch locationInView : self];
      //Gesture can be any of them:
      processSecondTap = YES;
   } 
}

//____________________________________________________________________________________________________
- (void) touchesMoved : (NSSet *) touches withEvent : (UIEvent *) event
{
#pragma unused(event)

   assert(touches != nil && "touchesMoved:withEvent:, parameter 'touches' is nil");
   assert(pad != nullptr && "touchesMoved:withEvent:, pad is null");

   if (!isMutable)
      return;

   if (panActive) {
      processSecondTap = NO;
      TObject *selected = pad->GetSelected();
      if (TAxis *axis = dynamic_cast<TAxis *>(selected)) {
         if (!selectionView.panActive) {
            selectionView.panActive = YES;
            if (!std::strcmp(axis->GetName(), "xaxis"))
               selectionView.verticalPanDirection = NO;
            else
               selectionView.verticalPanDirection = YES;
            selectionView.panStart = tapPt;
            
            pad->ExecuteEventAxis(kButton1Down, tapPt.x, tapPt.y, axis);
         } else {
            const CGPoint newPt = [[touches anyObject] locationInView : self];
            selectionView.currentPanPoint = newPt;
            pad->ExecuteEventAxis(kButton1Motion, newPt.x, newPt.y, axis);
            [selectionView setNeedsDisplay];
         }
      } else {
         //We move object in a canvas now.
      }
   }
}

//____________________________________________________________________________________________________
- (void) touchesEnded : (NSSet *) touches withEvent : (UIEvent *) event
{
#pragma unused(event)

   assert(touches != nil && "touchesEnded:withEvent:, parameter 'touches' is nil");
   assert(pad != nullptr && "touchesEnded:withEvent:, pad is null");

   if (!isMutable)
      return;

   UITouch * const touch = [touches anyObject];
   if (touch.tapCount == 1 && !panActive) {
      [self performSelector : @selector(handleSingleTap) withObject : nil afterDelay : tapInterval];
   } else if (touch.tapCount == 2 && processSecondTap) {
      //The second tap was done withing tapInterval, thus we
      //should process the gesture as a double tap.
      //Cancel handleSingleTap:
      [NSObject cancelPreviousPerformRequestsWithTarget : self];
      //
      [self handleDoubleTap];
   }

   if (selectionView.panActive) {
      panActive = NO;
      selectionView.panActive = NO;
      const CGPoint pt = [touch locationInView : self];
      pad->ExecuteEventAxis(kButton1Up, pt.x, pt.y, (TAxis *)pad->GetSelected());
      pad->InvalidateSelection(kTRUE);
      [self setNeedsDisplay];

      UIScrollView * const parent = (UIScrollView *)[self superview];
      parent.canCancelContentTouches = YES;
      parent.delaysContentTouches = YES;
   }
}

//____________________________________________________________________________________________________
- (void) handleDoubleTap
{
   //This is zoom/unzoom action or axis unzoom.
   
   assert(pad != nullptr && "handleDoubleTap, pad is null");
   
   if (!isMutable)
      return;
   
   const CGPoint scaledTapPt = [self scaledPoint : tapPt];
   TAxis * const axis = dynamic_cast<TAxis *>(pad->GetSelected());

   if (!pad->SelectionIsValid() && ![self initPadPicking])
      return;

   if (axis && pad->ObjectInPoint(scaledTapPt.x, scaledTapPt.y) == axis) {
      axis->UnZoom();
      pad->InvalidateSelection(kTRUE);
      [self setNeedsDisplay];
   } else {
      [controller handleDoubleTapOnPad : tapPt];
   }
   
   UIScrollView * const parent = (UIScrollView *)[self superview];
   parent.canCancelContentTouches = YES;
   parent.delaysContentTouches = YES;
}

@end
