//
// {{classname}}.m
//
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT DIRECTLY.
// See the corresponding header file for more information.
//

#import <CommonCrypto/CommonDigest.h>
{% if compressed %}
#import "zlib.h"
{% endif %}
#import "{{classname}}.h"

#pragma mark - Data

{{static_data}}


#pragma mark - Interface continuation

@interface {{classname}} ()

- (id)initPrivate;
- (NSDictionary *)expandBundle;
- (BOOL)saveBundle:(NSDictionary *)expandedBundle;
- (NSString *)bundlePath;
- (NSError *)selfTestErrorWithMessage:(NSString *)error;

@end


#pragma mark - Implementation

@implementation {{classname}}

+ ({{classname}} *)sharedInstance {
  static dispatch_once_t once;
  static {{classname}} *sharedInstance;
  dispatch_once(&once, ^ {
    sharedInstance = [[self alloc] initPrivate];
  });
  return sharedInstance;
}

- (id)initPrivate {
  self = [super init];
  if(self) {
    NSDictionary *expandedBundle = [self expandBundle];
    if(!expandedBundle) {
      return nil;
    }
    BOOL saved = [self saveBundle:expandedBundle];
    if(!saved) {
      return nil;
    }
  }
  return self;
}

- (id)init {
  [NSException raise:@"Singleton" format:@"{{classname}} is a singleton object. Use +sharedInstance instead."];
  return nil;
}

- (NSDictionary *)expandBundle {
  NSData *staticData = [NSData dataWithBytesNoCopy:{{static_data_var}} length:{{static_data_len}} freeWhenDone:NO];
{% if compressed %}
  NSMutableData *plistData = [NSMutableData dataWithLength:{{uncompressed_data_len}}];

  z_stream stream;
  // allocate inflate state
  stream.zalloc = Z_NULL;
  stream.zfree = Z_NULL;
  stream.opaque = Z_NULL;
  stream.avail_in = 0;
  stream.next_in = Z_NULL;
  if(inflateInit(&stream) != Z_OK) {
    return nil;
  }

  // set up our static var as the inflation input
  stream.avail_in = [staticData length];
  stream.next_in = (unsigned char *)[staticData bytes];

  // inflate into our pre-allocated buffer
  stream.avail_out = [plistData length];
  stream.next_out = (unsigned char *)[plistData bytes];
  int inflation_result = inflate(&stream, Z_NO_FLUSH);
  inflateEnd(&stream);
  if(inflation_result != Z_STREAM_END) {
    return nil;
  }

{% else %}
  NSData *plistData = staticData;
{% endif %}

  NSError *decodeError = nil;
  NSArray *filePairs = [NSPropertyListSerialization propertyListWithData:plistData
                                                                 options:NSPropertyListImmutable
                                                                  format:NULL
                                                                   error:&decodeError];
  if(decodeError) {
    return nil;
  }
  if(![filePairs isKindOfClass:[NSArray class]]) {
    return nil;
  }

  NSMutableDictionary *fileDict = [NSMutableDictionary dictionaryWithCapacity:[filePairs count]];
  for(NSArray *pair in filePairs) {
    if(![pair isKindOfClass:[NSArray class]] || [pair count] != 2) {
      return nil;
    }
    NSString *path = [pair objectAtIndex:0];
    NSData *data = [pair objectAtIndex:1];
    if(![path isKindOfClass:[NSString class]] || ![data isKindOfClass:[NSData class]]) {
      return nil;
    }
    // quick safety/sanity check
    if([path rangeOfString:@".."].location != NSNotFound) {
      return nil;
    }
    [fileDict setObject:data forKey:path];
  }

  return fileDict;
}

- (NSString *)bundlePath {
  // Use the caches dir, since this data can be recreated if it goes missing
  NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  if([cachePaths count] == 0) {
    return nil;
  }

  NSString *cachePath = [cachePaths objectAtIndex:0];
  NSString *bundleCachePath = [cachePath stringByAppendingPathComponent:@"{{classname}}.bundle"];
  return bundleCachePath;
}

- (BOOL)saveBundle:(NSDictionary *)expandedBundle {
  NSString *bundleCachePath = [self bundlePath];
  if(!bundleCachePath) {
    return NO;
  }

  NSError *subdirectoryCreationError = nil;
  BOOL subdirectoryCreated;
  subdirectoryCreated = [[NSFileManager defaultManager] createDirectoryAtPath:bundleCachePath
                                                  withIntermediateDirectories:YES
                                                                   attributes:nil
                                                                        error:&subdirectoryCreationError];
  if(!subdirectoryCreated || subdirectoryCreationError) {
    return NO;
  }

  for(NSString *filePath in expandedBundle) {
    NSString *fullPath = [bundleCachePath stringByAppendingPathComponent:filePath];

    NSString *subdirectory = [fullPath stringByDeletingLastPathComponent];
    subdirectoryCreated = [[NSFileManager defaultManager] createDirectoryAtPath:subdirectory
                                                    withIntermediateDirectories:YES
                                                                     attributes:nil
                                                                          error:&subdirectoryCreationError];
    if(!subdirectoryCreated || subdirectoryCreationError) {
      return NO;
    }

    NSData *fileData = [expandedBundle objectForKey:filePath];
    BOOL fileWritten = [fileData writeToFile:fullPath atomically:YES];
    if(!fileWritten) {
      return NO;
    }
  }

  return YES;
}

- (NSBundle *)NSBundle {
  return [NSBundle bundleWithPath:[self bundlePath]];
}

// What a PITA! I wish imageNamed: could be used with a user-specified bundle. :(
- (UIImage *)imageNamed:(NSString *)name {
  NSString *extension = [name pathExtension];
  NSString *dotExtension = [NSString stringWithFormat:@".%@", extension];
  NSString *basename = name;
  if([name hasSuffix:dotExtension]) {
    basename = [name substringToIndex:[name length] - [dotExtension length]];
  } else {
    dotExtension = @""; // preserve basename + dotExtension == name
  }

  NSString *interfaceIdiomSuffix = @"";
  if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
    interfaceIdiomSuffix = @"~iphone";
  } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    interfaceIdiomSuffix = @"~ipad";
  }

  NSString *resolutionSuffix = @"";
  CGFloat screenScale = [UIScreen mainScreen].scale;
  if(screenScale > 1.0f) {
    resolutionSuffix = [NSString stringWithFormat:@"@%dx", (int)lrint(screenScale)];
  }

  // Try in order:
  //   * Fully specific
  //   * Resolution only
  //   * UI Idiom only
  //   * Basename
  NSMutableArray *pathsToTry = [NSMutableArray arrayWithCapacity:4];
  if([resolutionSuffix length] > 0 && [interfaceIdiomSuffix length] > 0) {
    [pathsToTry addObject:[NSString stringWithFormat:@"%@%@%@%@", basename, resolutionSuffix, interfaceIdiomSuffix, dotExtension]];
  }
  if([resolutionSuffix length] > 0) {
    [pathsToTry addObject:[NSString stringWithFormat:@"%@%@%@", basename, resolutionSuffix, dotExtension]];
  }
  if([interfaceIdiomSuffix length] > 0) {
    [pathsToTry addObject:[NSString stringWithFormat:@"%@%@%@", basename, interfaceIdiomSuffix, dotExtension]];
  }
  [pathsToTry addObject:[NSString stringWithFormat:@"%@%@", basename, dotExtension]];

  NSBundle *bundle = self.NSBundle;
  UIImage *image = nil;
  for(NSString *pathToTry in pathsToTry) {
    NSString *bundlePath = [bundle pathForResource:pathToTry ofType:nil];
    image = [UIImage imageWithContentsOfFile:bundlePath];
    if(image) {
      // HACK: Ugly solution to an annoying problem: When do we need to adjust the resolution of the loaded image?
      if([resolutionSuffix length] > 0 && [pathToTry rangeOfString:resolutionSuffix].location != NSNotFound) {
        image = [UIImage imageWithCGImage:image.CGImage scale:screenScale orientation:UIImageOrientationUp];
      }
      break;
    }
  }

  return image;
}

- (NSError *)selfTestErrorWithMessage:(NSString *)errorMessage {
  return [NSError errorWithDomain:@"{{classname}}"
                             code:-1
                         userInfo:[NSDictionary dictionaryWithObject:errorMessage
                                                              forKey:NSLocalizedDescriptionKey]];

}

#if BALER_DEBUG

- (BOOL)passesSelfTest:(NSError * __autoreleasing *)error {
  NSBundle *bundle = [self NSBundle];

  NSDictionary *pathSHA1s = [NSDictionary dictionaryWithObjectsAndKeys:
{% for path, sha1 in sha1s %}
    @"{{sha1}}", @"{{path}}",
{% endfor %}
    nil];

  for(NSString *resourcePath in pathSHA1s) {
    NSString *path = [bundle pathForResource:resourcePath ofType:nil];
    NSData *data = [NSData dataWithContentsOfFile:path];
    if(!data) {
      if(error) {
        *error = [self selfTestErrorWithMessage:[NSString stringWithFormat:@"Could not find file %@ at path %@", resourcePath, path]];
      }
      return NO;
    }

    unsigned char calculatedSha1[CC_SHA1_DIGEST_LENGTH] = {0};
    CC_SHA1([data bytes], (CC_LONG)[data length], calculatedSha1);
    NSMutableString *calculatedSha1String = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
    for(NSInteger i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
      [calculatedSha1String appendFormat:@"%02x", calculatedSha1[i]];
    }

    NSString *expectedSha1String = [pathSHA1s objectForKey:resourcePath];
    if(![expectedSha1String isEqualToString:calculatedSha1String]) {
      if(error) {
        *error = [self selfTestErrorWithMessage:[NSString stringWithFormat:@"SHA1 mismatch for file %@: expected %@, got %@", resourcePath, expectedSha1String, calculatedSha1String]];
      }
      return NO;
    }
  }

  NSArray *knownImagePaths = [NSArray arrayWithObjects:
{% for image_path in image_paths %}
    @"{{image_path}}",
{% endfor %}
    nil];

  for(NSString *knownImagePath in knownImagePaths) {
    UIImage *image = [self imageNamed:knownImagePath];
    if(!image || ![image isKindOfClass:[UIImage class]]) {
      if(error) {
        *error = [self selfTestErrorWithMessage:[NSString stringWithFormat:@"Failed to load image %@: got %@", knownImagePath, image]];
      }
      return NO;
    }
  }

  // Test that bundles work for i18n/l10n (iff the bundled assets include a "en.lproj")
  if ([NSBundle bundleWithPath:[bundle pathForResource:@"en" ofType:@"lproj"]] != nil) {
    NSDictionary *thankYouTranslations = [NSDictionary dictionaryWithObjectsAndKeys:
      @"Thank you", @"en",
      @"Gracias", @"es",
      @"Maltyoox", @"tzj", // https://en.wikipedia.org/wiki/Tz'utujil_language
      nil
    ];

    for(NSString *lang in thankYouTranslations) {
      NSString *thanks = [thankYouTranslations objectForKey:lang];
      NSBundle *localizedBundle = [NSBundle bundleWithPath:[bundle pathForResource:lang ofType:@"lproj"]];
      NSString *translation = NSLocalizedStringWithDefaultValue(@"thank_you", @"baler-localized", localizedBundle, @"ta", nil);
      if(![thanks isEqualToString:translation]) {
        if(error) {
          *error = [self selfTestErrorWithMessage:[NSString stringWithFormat:@"The right translation for thank you in %@ is %@, received %@", lang, thanks, translation]];
        }
        return NO;
      }
    }
  }

  return YES;
}

#endif // #if BALER_DEBUG

@end
