/*
 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#import "APPRTCViewController.h"

#import <AVFoundation/AVFoundation.h>

#import "sdk/objc/api/peerconnection/RTCVideoTrack.h"
#import "sdk/objc/components/renderer/metal/RTCMTLNSVideoView.h"

#import "ARDAppClient.h"
#import "ARDCaptureController.h"
#import "ARDSettingsModel.h"

static NSUInteger const kContentWidth = 900;
static NSUInteger const kRoomFieldWidth = 200;
static NSUInteger const kActionItemHeight = 30;
static NSUInteger const kBottomViewHeight = 200;

@class APPRTCMainView;
@protocol APPRTCMainViewDelegate

- (void)appRTCMainView:(APPRTCMainView*)mainView
        didEnterRoomId:(NSString*)roomId
              loopback:(BOOL)isLoopback;

@end

@interface APPRTCMainView : NSView

@property(nonatomic, weak) id<APPRTCMainViewDelegate> delegate;
@property(nonatomic, readonly)
    NSView<RTC_OBJC_TYPE(RTCVideoRenderer)>* localVideoView;
@property(nonatomic, readonly)
    NSView<RTC_OBJC_TYPE(RTCVideoRenderer)>* remoteVideoView;
@property(nonatomic, readonly) NSTextView* logView;

- (void)displayLogMessage:(NSString*)message;

@end

@interface APPRTCMainView () <NSTextFieldDelegate,
                              RTC_OBJC_TYPE (RTCVideoViewDelegate)>
@end
@implementation APPRTCMainView {
  NSScrollView* _scrollView;
  NSView* _actionItemsView;
  NSButton* _connectButton;
  NSButton* _loopbackButton;
  NSTextField* _roomField;
  CGSize _localVideoSize;
  CGSize _remoteVideoSize;
}

@synthesize delegate = _delegate;
@synthesize localVideoView = _localVideoView;
@synthesize remoteVideoView = _remoteVideoView;
@synthesize logView = _logView;

- (void)displayLogMessage:(NSString*)message {
  dispatch_async(dispatch_get_main_queue(), ^{
    self.logView.string =
        [NSString stringWithFormat:@"%@%@\n", self.logView.string, message];
    NSRange range = NSMakeRange(self.logView.string.length, 0);
    [self.logView scrollRangeToVisible:range];
  });
}

#pragma mark - Private

- (instancetype)initWithFrame:(NSRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    [self setupViews];
  }
  return self;
}

+ (BOOL)requiresConstraintBasedLayout {
  return YES;
}

- (void)updateConstraints {
  NSParameterAssert(_roomField != nil && _scrollView != nil &&
                    _remoteVideoView != nil && _localVideoView != nil &&
                    _actionItemsView != nil && _connectButton != nil &&
                    _loopbackButton != nil);

  [self removeConstraints:[self constraints]];
  NSDictionary* viewsDictionary =
      NSDictionaryOfVariableBindings(_roomField,
                                     _scrollView,
                                     _remoteVideoView,
                                     _localVideoView,
                                     _actionItemsView,
                                     _connectButton,
                                     _loopbackButton);

  NSSize remoteViewSize = [self remoteVideoViewSize];
  NSDictionary* metrics = @{
    @"remoteViewWidth" : @(remoteViewSize.width),
    @"remoteViewHeight" : @(remoteViewSize.height),
    @"kBottomViewHeight" : @(kBottomViewHeight),
    @"localViewHeight" : @(remoteViewSize.height / 3),
    @"localViewWidth" : @(remoteViewSize.width / 3),
    @"kRoomFieldWidth" : @(kRoomFieldWidth),
    @"kActionItemHeight" : @(kActionItemHeight)
  };
  // Declare this separately to avoid compiler warning about splitting string
  // within an NSArray expression.
  NSString* verticalConstraintLeft = @"V:|-[_remoteVideoView(remoteViewHeight)]"
                                     @"-[_scrollView(kBottomViewHeight)]-|";
  NSString* verticalConstraintRight =
      @"V:|-[_remoteVideoView(remoteViewHeight)]-[_actionItemsView("
      @"kBottomViewHeight)]-|";
  NSArray* constraintFormats = @[
    verticalConstraintLeft,
    verticalConstraintRight,
    @"H:|-[_remoteVideoView(remoteViewWidth)]-|",
    @"V:|-[_localVideoView(localViewHeight)]",
    @"H:|-[_localVideoView(localViewWidth)]",
    @"H:|-[_scrollView(==_actionItemsView)]-[_actionItemsView]-|"
  ];

  NSArray* actionItemsConstraints = @[
    @"H:|-[_roomField(kRoomFieldWidth)]-[_loopbackButton(kRoomFieldWidth)]",
    @"H:|-[_connectButton(kRoomFieldWidth)]",
    @"V:|-[_roomField(kActionItemHeight)]-[_connectButton(kActionItemHeight)]",
    @"V:|-[_loopbackButton(kActionItemHeight)]",
  ];

  [APPRTCMainView addConstraints:constraintFormats
                          toView:self
                 viewsDictionary:viewsDictionary
                         metrics:metrics];
  [APPRTCMainView addConstraints:actionItemsConstraints
                          toView:_actionItemsView
                 viewsDictionary:viewsDictionary
                         metrics:metrics];
  [super updateConstraints];
}

#pragma mark - Constraints helper

+ (void)addConstraints:(NSArray*)constraintFormats
                toView:(NSView*)view
       viewsDictionary:(NSDictionary*)viewsDictionary
               metrics:(NSDictionary*)metrics {
  for (NSString* constraintFormat in constraintFormats) {
    NSArray* constraints =
        [NSLayoutConstraint constraintsWithVisualFormat:constraintFormat
                                                options:0
                                                metrics:metrics
                                                  views:viewsDictionary];
    for (NSLayoutConstraint* constraint in constraints) {
      [view addConstraint:constraint];
    }
  }
}

#pragma mark - Control actions

- (void)startCall:(id)sender {
  NSString* roomString = _roomField.stringValue;
  // Generate room id for loopback options.
  if (_loopbackButton.intValue && [roomString isEqualToString:@""]) {
    roomString = [NSUUID UUID].UUIDString;
    roomString = [roomString stringByReplacingOccurrencesOfString:@"-"
                                                       withString:@""];
  }
  [self.delegate appRTCMainView:self
                 didEnterRoomId:roomString
                       loopback:_loopbackButton.intValue];
  [self setNeedsUpdateConstraints:YES];
}

#pragma mark - RTCVideoViewDelegate

- (void)videoView:(id<RTC_OBJC_TYPE(RTCVideoRenderer)>)videoView
    didChangeVideoSize:(CGSize)size {
  if (videoView == _remoteVideoView) {
    _remoteVideoSize = size;
  } else if (videoView == _localVideoView) {
    _localVideoSize = size;
  } else {
    return;
  }

  [self setNeedsUpdateConstraints:YES];
}

#pragma mark - Private

- (void)setupViews {
  NSParameterAssert([[self subviews] count] == 0);

  _logView = [[NSTextView alloc] initWithFrame:NSZeroRect];
  [_logView setMinSize:NSMakeSize(0, kBottomViewHeight)];
  [_logView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
  [_logView setVerticallyResizable:YES];
  [_logView setAutoresizingMask:NSViewWidthSizable];
  NSTextContainer* textContainer = [_logView textContainer];
  NSSize containerSize = NSMakeSize(kContentWidth, FLT_MAX);
  [textContainer setContainerSize:containerSize];
  [textContainer setWidthTracksTextView:YES];
  [_logView setEditable:NO];

  [self setupActionItemsView];

  _scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect];
  [_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
  [_scrollView setHasVerticalScroller:YES];
  [_scrollView setDocumentView:_logView];
  [self addSubview:_scrollView];

  _remoteVideoView =
      [[RTC_OBJC_TYPE(RTCMTLNSVideoView) alloc] initWithFrame:NSZeroRect];
  _localVideoView =
      [[RTC_OBJC_TYPE(RTCMTLNSVideoView) alloc] initWithFrame:NSZeroRect];

  [_remoteVideoView setTranslatesAutoresizingMaskIntoConstraints:NO];
  [self addSubview:_remoteVideoView];
  [_localVideoView setTranslatesAutoresizingMaskIntoConstraints:NO];
  [self addSubview:_localVideoView];
}

- (void)setupActionItemsView {
  _actionItemsView = [[NSView alloc] initWithFrame:NSZeroRect];
  [_actionItemsView setTranslatesAutoresizingMaskIntoConstraints:NO];
  [self addSubview:_actionItemsView];

  _roomField = [[NSTextField alloc] initWithFrame:NSZeroRect];
  [_roomField setTranslatesAutoresizingMaskIntoConstraints:NO];
  [[_roomField cell] setPlaceholderString:@"Enter AppRTC room id"];
  [_actionItemsView addSubview:_roomField];
  [_roomField setEditable:YES];

  _connectButton = [[NSButton alloc] initWithFrame:NSZeroRect];
  [_connectButton setTranslatesAutoresizingMaskIntoConstraints:NO];
  _connectButton.title = @"Start call";
  _connectButton.bezelStyle = NSBezelStyleSmallSquare;
  _connectButton.target = self;
  _connectButton.action = @selector(startCall:);
  [_actionItemsView addSubview:_connectButton];

  _loopbackButton = [[NSButton alloc] initWithFrame:NSZeroRect];
  [_loopbackButton setTranslatesAutoresizingMaskIntoConstraints:NO];
  _loopbackButton.title = @"Loopback";
  [_loopbackButton setButtonType:NSButtonTypeSwitch];
  [_actionItemsView addSubview:_loopbackButton];
}

- (NSSize)remoteVideoViewSize {
  if (!_remoteVideoView.bounds.size.width) {
    return NSMakeSize(kContentWidth, 0);
  }
  NSInteger width = MAX(_remoteVideoView.bounds.size.width, kContentWidth);
  NSInteger height = (width / 16) * 9;
  return NSMakeSize(width, height);
}

@end

@interface APPRTCViewController () <ARDAppClientDelegate,
                                    APPRTCMainViewDelegate>
@property(nonatomic, readonly) APPRTCMainView* mainView;
@end

@implementation APPRTCViewController {
  ARDAppClient* _client;
  RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack;
  RTC_OBJC_TYPE(RTCVideoTrack) * _remoteVideoTrack;
  ARDCaptureController* _captureController;
}

- (void)dealloc {
  [self disconnect];
}

- (void)viewDidAppear {
  [super viewDidAppear];
  [self displayUsageInstructions];
}

- (void)loadView {
  APPRTCMainView* view = [[APPRTCMainView alloc] initWithFrame:NSZeroRect];
  [view setTranslatesAutoresizingMaskIntoConstraints:NO];
  view.delegate = self;
  self.view = view;
}

- (void)windowWillClose:(NSNotification*)notification {
  [self disconnect];
}

#pragma mark - Usage

- (void)displayUsageInstructions {
  [self.mainView displayLogMessage:
                     @"To start call:\n"
                     @"• Enter AppRTC room id (not neccessary for loopback)\n"
                     @"• Start call"];
}

#pragma mark - ARDAppClientDelegate

- (void)appClient:(ARDAppClient*)client
    didChangeState:(ARDAppClientState)state {
  switch (state) {
    case kARDAppClientStateConnected:
      [self.mainView displayLogMessage:@"Client connected."];
      break;
    case kARDAppClientStateConnecting:
      [self.mainView displayLogMessage:@"Client connecting."];
      break;
    case kARDAppClientStateDisconnected:
      [self.mainView displayLogMessage:@"Client disconnected."];
      [self resetUI];
      _client = nil;
      break;
  }
}

- (void)appClient:(ARDAppClient*)client
    didChangeConnectionState:(RTCIceConnectionState)state {
}

- (void)appClient:(ARDAppClient*)client
    didCreateLocalCapturer:
        (RTC_OBJC_TYPE(RTCCameraVideoCapturer) *)localCapturer {
  _captureController = [[ARDCaptureController alloc]
      initWithCapturer:localCapturer
              settings:[[ARDSettingsModel alloc] init]];
  [_captureController startCapture];
}

- (void)appClient:(ARDAppClient*)client
    didReceiveLocalVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)localVideoTrack {
  _localVideoTrack = localVideoTrack;
  [_localVideoTrack addRenderer:self.mainView.localVideoView];
}

- (void)appClient:(ARDAppClient*)client
    didReceiveRemoteVideoTrack:
        (RTC_OBJC_TYPE(RTCVideoTrack) *)remoteVideoTrack {
  _remoteVideoTrack = remoteVideoTrack;
  [_remoteVideoTrack addRenderer:self.mainView.remoteVideoView];
}

- (void)appClient:(ARDAppClient*)client didError:(NSError*)error {
  [self showAlertWithMessage:[NSString stringWithFormat:@"%@", error]];
  [self disconnect];
}

- (void)appClient:(ARDAppClient*)client didGetStats:(NSArray*)stats {
}

#pragma mark - APPRTCMainViewDelegate

- (void)appRTCMainView:(APPRTCMainView*)mainView
        didEnterRoomId:(NSString*)roomId
              loopback:(BOOL)isLoopback {
  if ([roomId isEqualToString:@""]) {
    [self.mainView displayLogMessage:@"Missing room id"];
    return;
  }

  [self disconnect];
  ARDAppClient* client = [[ARDAppClient alloc] initWithDelegate:self];
  [client connectToRoomWithId:roomId
                     settings:[[ARDSettingsModel alloc]
                                  init]  // Use default settings.
                   isLoopback:isLoopback];
  _client = client;
}

#pragma mark - Private

- (APPRTCMainView*)mainView {
  return (APPRTCMainView*)self.view;
}

- (void)showAlertWithMessage:(NSString*)message {
  dispatch_async(dispatch_get_main_queue(), ^{
    NSAlert* alert = [[NSAlert alloc] init];
    [alert setMessageText:message];
    [alert runModal];
  });
}

- (void)resetUI {
  [_remoteVideoTrack removeRenderer:self.mainView.remoteVideoView];
  [_localVideoTrack removeRenderer:self.mainView.localVideoView];
  _remoteVideoTrack = nil;
  _localVideoTrack = nil;
  [self.mainView.remoteVideoView renderFrame:nil];
  [self.mainView.localVideoView renderFrame:nil];
}

- (void)disconnect {
  [self resetUI];
  [_captureController stopCapture];
  _captureController = nil;
  [_client disconnect];
}

@end
