/*
 * libjingle
 * Copyright 2013, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "APPRTCAppClient.h"

#import <dispatch/dispatch.h>

#import "GAEChannelClient.h"
#import "RTCICEServer.h"
#import "APPRTCAppDelegate.h"
#import "RTCMediaConstraints.h"

@interface APPRTCAppClient ()

@property(nonatomic, strong) dispatch_queue_t backgroundQueue;
@property(nonatomic, copy) NSString* baseURL;
@property(nonatomic, strong) GAEChannelClient* gaeChannel;
@property(nonatomic, copy) NSString* postMessageUrl;
@property(nonatomic, copy) NSString* pcConfig;
@property(nonatomic, strong) NSMutableString* roomHtml;
@property(atomic, strong) NSMutableArray* sendQueue;
@property(nonatomic, copy) NSString* token;

@property(nonatomic, assign) BOOL verboseLogging;

@end

@implementation APPRTCAppClient

- (id)initWithICEServerDelegate:(id<ICEServerDelegate>)delegate
                 messageHandler:(id<GAEMessageHandler>)handler {
  if (self = [super init]) {
    _ICEServerDelegate = delegate;
    _messageHandler = handler;
    _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue", NULL);
    _sendQueue = [NSMutableArray array];
    // Uncomment to see Request/Response logging.
    // _verboseLogging = YES;
  }
  return self;
}

#pragma mark - Public methods

- (void)connectToRoom:(NSURL*)url {
  NSURLRequest* request = [self getRequestFromUrl:url];
  [NSURLConnection connectionWithRequest:request delegate:self];
}

- (void)sendData:(NSData*)data {
  @synchronized(self) {
    [self maybeLogMessage:@"Send message"];
    [self.sendQueue addObject:[data copy]];
  }
  [self requestQueueDrainInBackground];
}

#pragma mark - Internal methods

- (NSString*)findVar:(NSString*)name strippingQuotes:(BOOL)strippingQuotes {
  NSError* error;
  NSString* pattern =
      [NSString stringWithFormat:@".*\n *var %@ = ([^\n]*);\n.*", name];
  NSRegularExpression* regexp =
      [NSRegularExpression regularExpressionWithPattern:pattern
                                                options:0
                                                  error:&error];
  NSAssert(!error,
           @"Unexpected error compiling regex: ",
           error.localizedDescription);

  NSRange fullRange = NSMakeRange(0, [self.roomHtml length]);
  NSArray* matches =
      [regexp matchesInString:self.roomHtml options:0 range:fullRange];
  if ([matches count] != 1) {
    [self showMessage:[NSString stringWithFormat:@"%d matches for %@ in %@",
                                                 [matches count],
                                                 name,
                                                 self.roomHtml]];
    return nil;
  }
  NSRange matchRange = [matches[0] rangeAtIndex:1];
  NSString* value = [self.roomHtml substringWithRange:matchRange];
  if (strippingQuotes) {
    NSAssert([value length] > 2,
             @"Can't strip quotes from short string: [%@]",
             value);
    NSAssert(([value characterAtIndex:0] == '\'' &&
              [value characterAtIndex:[value length] - 1] == '\''),
             @"Can't strip quotes from unquoted string: [%@]",
             value);
    value = [value substringWithRange:NSMakeRange(1, [value length] - 2)];
  }
  return value;
}

- (NSURLRequest*)getRequestFromUrl:(NSURL*)url {
  self.roomHtml = [NSMutableString stringWithCapacity:20000];
  NSString* path =
      [NSString stringWithFormat:@"https:%@", [url resourceSpecifier]];
  NSURLRequest* request =
      [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
  return request;
}

- (void)maybeLogMessage:(NSString*)message {
  if (self.verboseLogging) {
    NSLog(@"%@", message);
  }
}

- (void)requestQueueDrainInBackground {
  dispatch_async(self.backgroundQueue, ^(void) {
      // TODO(hughv): This can block the UI thread.  Fix.
      @synchronized(self) {
        if ([self.postMessageUrl length] < 1) {
          return;
        }
        for (NSData* data in self.sendQueue) {
          NSString* url =
            [NSString stringWithFormat:@"%@/%@",
                      self.baseURL, self.postMessageUrl];
          [self sendData:data withUrl:url];
        }
        [self.sendQueue removeAllObjects];
      }
    });
}

- (void)sendData:(NSData*)data withUrl:(NSString*)url {
  NSMutableURLRequest* request =
      [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
  request.HTTPMethod = @"POST";
  [request setHTTPBody:data];
  NSURLResponse* response;
  NSError* error;
  NSData* responseData = [NSURLConnection sendSynchronousRequest:request
                                               returningResponse:&response
                                                           error:&error];
  NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
  int status = [httpResponse statusCode];
  NSAssert(status == 200,
           @"Bad response [%d] to message: %@\n\n%@",
           status,
           [NSString stringWithUTF8String:[data bytes]],
           [NSString stringWithUTF8String:[responseData bytes]]);
}

- (void)showMessage:(NSString*)message {
  NSLog(@"%@", message);
  UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Unable to join"
                                                      message:message
                                                     delegate:nil
                                            cancelButtonTitle:@"OK"
                                            otherButtonTitles:nil];
  [alertView show];
}

- (void)updateICEServers:(NSMutableArray*)ICEServers
          withTurnServer:(NSString*)turnServerUrl {
  if ([turnServerUrl length] < 1) {
    [self.ICEServerDelegate onICEServers:ICEServers];
    return;
  }
  dispatch_async(self.backgroundQueue, ^(void) {
      NSMutableURLRequest* request = [NSMutableURLRequest
          requestWithURL:[NSURL URLWithString:turnServerUrl]];
      [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
      [request addValue:@"https://apprtc.appspot.com"
          forHTTPHeaderField:@"origin"];
      NSURLResponse* response;
      NSError* error;
      NSData* responseData = [NSURLConnection sendSynchronousRequest:request
                                                   returningResponse:&response
                                                               error:&error];
      if (!error) {
        NSDictionary* json =
            [NSJSONSerialization JSONObjectWithData:responseData
                                            options:0
                                              error:&error];
        NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
        NSString* username = json[@"username"];
        NSString* password = json[@"password"];
        NSArray* uris = json[@"uris"];
        for (int i = 0; i < [uris count]; ++i) {
          NSString* turnServer = [uris objectAtIndex:i];
          RTCICEServer* ICEServer =
              [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:turnServer]
                                       username:username
                                       password:password];
          NSLog(@"Added ICE Server: %@", ICEServer);
          [ICEServers addObject:ICEServer];
        }
      } else {
        NSLog(@"Unable to get TURN server.  Error: %@", error.description);
      }

      dispatch_async(dispatch_get_main_queue(), ^(void) {
          [self.ICEServerDelegate onICEServers:ICEServers];
      });
  });
}

#pragma mark - NSURLConnectionDataDelegate methods

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
  NSString* roomHtml = [NSString stringWithUTF8String:[data bytes]];
  [self maybeLogMessage:[NSString stringWithFormat:@"Received %d chars",
                                                   [roomHtml length]]];
  [self.roomHtml appendString:roomHtml];
}

- (void)connection:(NSURLConnection*)connection
    didReceiveResponse:(NSURLResponse*)response {
  NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
  int statusCode = [httpResponse statusCode];
  [self
      maybeLogMessage:
          [NSString stringWithFormat:
                        @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
                        [httpResponse URL],
                        statusCode,
                        [httpResponse allHeaderFields]]];
  NSAssert(statusCode == 200, @"Invalid response  of %d received.", statusCode);
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
  [self maybeLogMessage:[NSString stringWithFormat:@"finished loading %d chars",
                                                   [self.roomHtml length]]];
  NSRegularExpression* fullRegex =
      [NSRegularExpression regularExpressionWithPattern:@"room is full"
                                                options:0
                                                  error:nil];
  if ([fullRegex
          numberOfMatchesInString:self.roomHtml
                          options:0
                            range:NSMakeRange(0, [self.roomHtml length])]) {
    [self showMessage:@"Room full"];
    APPRTCAppDelegate* ad =
        (APPRTCAppDelegate*)[[UIApplication sharedApplication] delegate];
    [ad closeVideoUI];
    return;
  }

  NSString* fullUrl = [[[connection originalRequest] URL] absoluteString];
  NSRange queryRange = [fullUrl rangeOfString:@"?"];
  self.baseURL = [fullUrl substringToIndex:queryRange.location];
  [self maybeLogMessage:[NSString
                            stringWithFormat:@"Base URL: %@", self.baseURL]];

  self.initiator = [[self findVar:@"initiator" strippingQuotes:NO] boolValue];
  self.token = [self findVar:@"channelToken" strippingQuotes:YES];
  if (!self.token)
    return;
  [self maybeLogMessage:[NSString stringWithFormat:@"Token: %@", self.token]];

  NSString* roomKey = [self findVar:@"roomKey" strippingQuotes:YES];
  NSString* me = [self findVar:@"me" strippingQuotes:YES];
  if (!roomKey || !me)
    return;
  self.postMessageUrl =
      [NSString stringWithFormat:@"/message?r=%@&u=%@", roomKey, me];
  [self maybeLogMessage:[NSString stringWithFormat:@"POST message URL: %@",
                                                   self.postMessageUrl]];

  NSString* pcConfig = [self findVar:@"pcConfig" strippingQuotes:NO];
  if (!pcConfig)
    return;
  [self maybeLogMessage:[NSString
                            stringWithFormat:@"PC Config JSON: %@", pcConfig]];

  NSString* turnServerUrl = [self findVar:@"turnUrl" strippingQuotes:YES];
  if (turnServerUrl) {
    [self maybeLogMessage:[NSString
                              stringWithFormat:@"TURN server request URL: %@",
                                               turnServerUrl]];
  }

  NSError* error;
  NSData* pcData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
  NSDictionary* json =
      [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error];
  NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
  NSArray* servers = [json objectForKey:@"iceServers"];
  NSMutableArray* ICEServers = [NSMutableArray array];
  for (NSDictionary* server in servers) {
    NSString* url = [server objectForKey:@"urls"];
    NSString* username = json[@"username"];
    NSString* credential = [server objectForKey:@"credential"];
    if (!username) {
      username = @"";
    }
    if (!credential) {
      credential = @"";
    }
    [self maybeLogMessage:[NSString
                              stringWithFormat:@"url [%@] - credential [%@]",
                                               url,
                                               credential]];
    RTCICEServer* ICEServer =
        [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url]
                                 username:username
                                 password:credential];
    NSLog(@"Added ICE Server: %@", ICEServer);
    [ICEServers addObject:ICEServer];
  }
  [self updateICEServers:ICEServers withTurnServer:turnServerUrl];

  NSString* mc = [self findVar:@"mediaConstraints" strippingQuotes:NO];
  if (mc) {
    error = nil;
    NSData* mcData = [mc dataUsingEncoding:NSUTF8StringEncoding];
    json =
        [NSJSONSerialization JSONObjectWithData:mcData options:0 error:&error];
    NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
    if ([[json objectForKey:@"video"] boolValue]) {
      _videoConstraints = [[RTCMediaConstraints alloc] init];
    }
  }

  [self
      maybeLogMessage:[NSString
                          stringWithFormat:@"About to open GAE with token:  %@",
                                           self.token]];
  self.gaeChannel =
      [[GAEChannelClient alloc] initWithToken:self.token
                                     delegate:self.messageHandler];
}

@end
