#import "ImportProgressController.h"
#import "AppDelegate.h"
#import "Tune.h"

@implementation ImportProgressController

-(void)viewDidLoad {
  tune = NULL;
  lineData = NULL;
}

- (void)import:(NSURL *)url
{
  progressView.progress = 0;
  _url = url;
  [_url retain];
  [NSThread detachNewThreadSelector:@selector(threadRun:) toTarget:self withObject:nil];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
  return YES;
}

- (void)didReceiveMemoryWarning
{
	[super didReceiveMemoryWarning];
}

-(void)dealloc
{
  [_url release];
	[super dealloc];
}

- (IBAction) onCancel:(id)sender
{
  cancelled = true;
  if ( urlConnection ) {
    [urlConnection cancel];
    urlConnection = NULL;
  }
}

- (void)threadRun:(id)sender
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  

  cancelled = false;

  NSURLRequest *theRequest = [NSURLRequest requestWithURL:_url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
  
  bytesRead = 0;
  numTunes = 0;

//  urlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:NO];
  urlConnection = [NSURLConnection connectionWithRequest:theRequest delegate:self];
  if ( urlConnection == nil ) {
    UIAlertView *alert = [[UIAlertView alloc] 
      initWithTitle: @"Unable to initiate request" 
      message:@"NSURLConnection initialization method failed" 
      delegate:self 
      cancelButtonTitle:nil 
      otherButtonTitles:@"OK", nil];
    [alert show];
  }

//  [NSRunLoop currentRunLoop];

  BOOL done = NO;
  do {
    SInt32 result = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 1, YES );
    if ( (result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished) ) {
      done = YES;
    }
    if ( cancelled )
      done = YES;
  } while ( !done );

  [pool release];
}

#pragma mark UIViewController delegate methods

- (void)viewWillAppear:(BOOL)animated
{
}

#pragma mark NSURLConnection delegate methods

- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
  bytesToRead = (long)[response expectedContentLength];
}

- (void) parseLine:(NSString *)line
{
//  printf("line: '%s'\n", [line UTF8String]);

  AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

  if ( [line compare:@"X:" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 2)] == 0 ) {
    // Started a tune
    tune = [[Tune alloc] init];
    tune.title = NULL;
    tune.key = @"";
    tune.dots = NULL;
    dots = [[NSMutableString alloc] init];
    [dots appendString:@"X:1\n"];
  } else if ( dots ) {
    if ( [line compare:@"T:" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 2)] == 0 ) {
      if ( !tune.title )
        tune.title = [self fixAccents:[[line substringFromIndex:2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
      [dots appendString:line];
      [dots appendString:@"\n"];
    } else if ( [line compare:@"K:" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 2)] == 0 ) {
      if ( !tune.key )
        tune.key = [[line substringFromIndex:2]
          stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]
        ];
      [dots appendString:line];
      [dots appendString:@"\n"];
    } else if ( [line compare:@"M:" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 2)] == 0 ) {
      NSString *s = [[line substringFromIndex:2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
      const char *p = [s UTF8String];
      int n, d;
      if ( sscanf( p, "%d/%d", &n, &d ) == 2 ) {
        tune.timeSigNum = n;
        tune.timeSigDen = d;
      } else if ( *p == 'C' ) {
        if ( p[1] == '|' )
          tune.timeSigNum = tune.timeSigDen = 2;
        else
          tune.timeSigNum = tune.timeSigDen = 4;
      }
      [dots appendString:line];
      [dots appendString:@"\n"];
    } else if ( [line length] == 0 ) {
//printf("%d: %s\n", numTunes, [tune.title UTF8String] );
      tune.dots = dots;
      if ( tune.title && dots ) {
        [appDelegate addTune: tune];
        numTunes++;
      }
      dots = NULL;
      tune = NULL;
    } else {
      [dots appendString:line];
      [dots appendString:@"\n"];
    }
  }
}

// FIXME: we should at least do a binary search, or compile this table into a state machine
struct Accent {
  const char *text;
  const unichar replacement;
};

struct Accent accents[] = {
  { "`a", 0xe0, },
  { "'a", 0xe1, },
  { "^a", 0xe2, },
  { "\"a", 0xe3, },
  { "~a", 0xe3, },
  { "oa", 0xe5, },
  { "=a", 0x101, },
  { "ua", 0x103, },
  { ";a", 0x105, },
  { "`A", 0xc0, },
  { "'A", 0xc1, },
  { "^A", 0xc2, },
  { "\"A", 0xc3, },
  { "~A", 0xc3, },
  { "oA", 0xc5, },
  { "=A", 0x100, },
  { "uA", 0x102, },
  { ";A", 0x104, },

  { ",c", 0xe7, },
  { "'c", 0x107, },
  { "^c", 0x109, },
  { ".c", 0x10b },
  { "vc", 0x10d, },
  { "'C", 0x106, },
  { "^C", 0x108, },
  { ",C", 0xc7, },
  { ".C", 0x10a },
  { "vC", 0x10c, },

  { "vd", 0x10f, },
  { "=d", 0x111, },
  { "vD", 0x10e, },
  { "=D", 0x110, },

  { "`e", 0xe8, },
  { "'e", 0xe9, },
  { "^e", 0xea, },
  { "\"e", 0xeb, },
  { "=e", 0x113, },
  { "ue", 0x115, },
  { ";e", 0x119, },
  { ".e", 0x117, },
  { "`E", 0xc8, },
  { "'E", 0xc9, },
  { "^E", 0xca, },
  { "\"E", 0xcb, },
  { "=E", 0x112, },
  { "uE", 0x114, },
  { ";E", 0x118, },
  { ".E", 0x116, },

  { "^g", 0x11d, },
  { "ug", 0x11f, },
  { ".g", 0x121, },
  { ",g", 0x123, },
  { "^G", 0x11c, },
  { "uG", 0x11e, },
  { ".G", 0x120, },
  { ",G", 0x122, },

  { "^h", 0x125, },
  { "=h", 0x127, },
  { "^H", 0x124, },
  { "=H", 0x126, },

  { "`i", 0xec, },
  { "'i", 0xed, },
  { "^i", 0xee, },
  { "\"i", 0xef, },
  { "~i", 0x129, },
  { "=i", 0x12b, },
  { "ui", 0x12d, },
  { ".i", 0x131, },
  { ";i", 0x12f, },
  { "`I", 0xcc, },
  { "'I", 0xcd, },
  { "^I", 0xce, },
  { "\"I", 0xcf, },
  { "~I", 0x128, },
  { "=I", 0x12a, },
  { "uI", 0x12c, },
  { ".I", 0x130, },
  { ";I", 0x12e, },

  { "~k", 0x137, },
  { "~K", 0x136, },

  { "'l", 0x13a, },
  { ",l", 0x13c, },
  { "vl", 0x13e, },
  { ".l", 0x140, },
  { "/l", 0x142, },
  { "'L", 0x139, },
  { ",L", 0x13b, },
  { "vL", 0x13d, },
  { ".L", 0x13f, },
  { "/L", 0x141, },

  { "~n", 0xf1, },
  { "'n", 0x144, },
  { ",n", 0x146, },
  { "vn", 0x148, },
  { "~N", 0xd1, },
  { "'N", 0x143, },
  { ",N", 0x145, },
  { "vN", 0x147, },

  { "`o", 0xf2, },
  { "'o", 0xf3, },
  { "^o", 0xf4, },
  { "\"o", 0xf6, },
  { "~o", 0xf5, },
  { "=o", 0x14d, },
  { "/o", 0xf8, },
  { "uo", 0x14f, },
  { ":o", 0x151, },
  { "`O", 0xd2, },
  { "'O", 0xd3, },
  { "^O", 0xd4, },
  { "\"O", 0xd6, },
  { "~O", 0xd5, },
  { "=O", 0x14c, },
  { "/O", 0xd8, },
  { "uO", 0x14e, },
  { ":O", 0x150, },

  { "'r", 0x155, },
  { ",r", 0x157, },
  { "vr", 0x159, },
  { "'R", 0x154, },
  { ",R", 0x156, },
  { "vR", 0x158, },

  { "'s", 0x15b, },
  { "^s", 0x15d, },
  { ",s", 0x15f, },
  { "vs", 0x161, },
  { "'S", 0x15a, },
  { "^S", 0x15c, },
  { ",S", 0x15e, },
  { "vS", 0x160, },

  { ",t", 0x163, },
  { "vt", 0x165, },
  { "=t", 0x167, },
  { ",T", 0x162, },
  { "vT", 0x164, },
  { "=T", 0x166, },

  { "`u", 0xf9, },
  { "'u", 0xfa, },
  { "^u", 0xfb, },
  { "\"u", 0xfc, },
  { "~u", 0x169, },
  { "=u", 0x16b, },
  { "uu", 0x16d, },
  { "ou", 0x16f, },
  { ":u", 0x171, },
  { ";u", 0x173, },
  { "`U", 0xd9, },
  { "'U", 0xda, },
  { "^U", 0xdb, },
  { "\"U", 0xdc, },
  { "~U", 0x168, },
  { "=U", 0x16b, },
  { "uU", 0x16c, },
  { "oU", 0x16e, },
  { ":U", 0x170, },
  { ";U", 0x172, },

  { "^w", 0x175, },
  { "^W", 0x174, },

  { "\"y", 0xff, },
  { "^y", 0x176, },
  { "^Y", 0x175, },
  { "\"Y", 0x17b, },

  { "'z", 0x17a, },
  { ".z", 0x17c, },
  { "vz", 0x17e, },
  { "'Z", 0x179, },
  { ".Z", 0x17b, },
  { "vZ", 0x17d, },

  { "ae", 0xe6, },
  { "AE", 0xc6, },

  { "oe", 0x153, },
  { "OE", 0x152, },

  { "ss", 0xdf, },

  { "ng", 0x14b, },
  { "NG", 0x14a, },

  { NULL, 0 }
};

- (NSString *)fixAccents:(NSString *)s
{
  const char *p = [s UTF8String];
  while ( *p ) {
    if ( *p++ == '\\' ) {
      char buf[2048];
      p = [s UTF8String];
      int k = 0;
      while ( *p && k < sizeof(buf)-2 ) {
        char c = *p++;
        if ( c == '\\' ) {
          bool done = false;
          for ( int a = 0; accents[a].text; a++ ) {
            const char *ap = accents[a].text;
            const char *sp = p;
            while ( *ap && *ap == *sp ) {
              ap++;
              sp++;
            }
            if ( !*ap ) {
              unichar ua = accents[a].replacement;
              buf[k++] = 0xc0 | ua >> 6;
              buf[k++] = 0x80 | (ua & 0x3f);
              p = sp;
              done = true;
              break;
            }
          }
          if ( !done ) 
            buf[k++] = c;
        } else
          buf[k++] = c;
      }
      buf[k++] = 0;
      return [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
    }
  }

  return s;
}

- (void) updateProgress:(id)sender
{
  progressView.progress = (float)bytesRead / bytesToRead;
  statusLabel.text = [NSString stringWithFormat:@"Read %.1fKb, found %d tunes", (float)bytesRead/1024, numTunes];
}

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
  const char *b = (const char *)data.bytes;
  int length = data.length;
  int start = 0;

  if ( cancelled )
    return;

  bytesRead += length;
  [self performSelectorOnMainThread:@selector(updateProgress:) withObject:nil waitUntilDone:NO];

  if ( !lineData )
    lineData = [[NSMutableData alloc] initWithLength:0];

  int n = numTunes;
  for ( int i = 0; i < length && !cancelled; i++ ) {
    if ( b[i] == '\r' || b[i] == '\n' ) {
      [lineData appendBytes:b+start length:i-start];
      NSString *line = [[NSString alloc] initWithBytes:b+start length:i-start encoding:NSISOLatin1StringEncoding];
      [self parseLine:line];
      if ( numTunes >= n+10 ) {
        [self performSelectorOnMainThread:@selector(updateProgress:) withObject:nil waitUntilDone:NO];
        n = numTunes;
      }

      [line release];
      [lineData setLength: 0];
      if ( b[i] == '\r' && i < length-1 && b[i+1] == '\n' )
        i++;
      start = i+1;
    }
  }
}

- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
  UIAlertView *alert = [[UIAlertView alloc] 
    initWithTitle: @"Import Failed"
    message:[error localizedDescription]
    delegate:self
    cancelButtonTitle:nil
    otherButtonTitles:@"OK", nil];
  [alert show];
	[connection release];
  [self dismissModalViewControllerAnimated:YES];
}

- (NSCachedURLResponse *) connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
  return nil;
}

- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
  [self parseLine:@"\n"]; // Force the last tune to end

  if ( numTunes ) {
    UIAlertView *alert = [[UIAlertView alloc] 
      initWithTitle: cancelled ? @"Import Stopped" : @"Import Succeeded" 
      message:[NSString stringWithFormat:@"Imported %d tunes", numTunes]
      delegate:self 
      cancelButtonTitle:nil 
      otherButtonTitles:@"OK", nil]; 
    [alert show];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"databaseChanged" object:self]; 
  } else {
    UIAlertView *alert = [[UIAlertView alloc] 
      initWithTitle: @"Import Failed" 
      message:@"The file was downloaded, but appeared to contain no tunes."
      delegate:self 
      cancelButtonTitle:nil 
      otherButtonTitles:@"OK", nil]; 
    [alert show];
  }

//  [self dismissModalViewControllerAnimated:YES];
}

#pragma mark UIAlertView delegate methods

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
  [self dismissModalViewControllerAnimated:YES];
}

@end
