#import <CommonCrypto/CommonDigest.h>
#import "UpdateHashUtils.h"

@implementation UpdateHashUtils : NSObject

+ (NSArray *)ignoredFilenames {
    return @[
            @".codepushrelease",
            @".DS_Store",
            @"__MACOSX"
    ];
}

+ (NSString*)binaryAssetsPath
{
    return [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"www"];
}

+ (void)addFolderEntriesToManifest:(NSString*)folderPath
                        pathPrefix:(NSString*)pathPrefix
                   manifestEntries:(NSMutableArray*)manifestEntries
                             error:(NSError**)error
{
    NSArray* folderFiles = [[NSFileManager defaultManager]
            contentsOfDirectoryAtPath:folderPath
                                error:error];
    if (*error) {
        return;
    }

    for (NSString* fileName in folderFiles) {
        if ([[self ignoredFilenames] containsObject:fileName]) {
            continue;
        }
        NSString* fullFilePath = [folderPath stringByAppendingPathComponent:fileName];
        NSString* relativePath = [pathPrefix stringByAppendingPathComponent:fileName];
        BOOL isDir = NO;
        if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath
                                                 isDirectory:&isDir] && isDir) {
            [self addFolderEntriesToManifest:fullFilePath
                                  pathPrefix:relativePath
                             manifestEntries:manifestEntries
                                       error:error];
            if (*error) {
                return;
            }
        } else {
            NSData* fileContents = [NSData dataWithContentsOfFile:fullFilePath];
            NSString* fileContentsHash = [self computeHashForData:fileContents];
            [manifestEntries addObject:[[relativePath stringByAppendingString:@":"] stringByAppendingString:fileContentsHash]];
        }
    }
}

+ (NSString*)computeFinalHashFromManifestEntries:(NSMutableArray*)manifest
                                           error:(NSError**)error
{
    NSArray* sortedManifest = [manifest sortedArrayUsingSelector:@selector(compare:)];
    NSData* manifestData = [NSJSONSerialization dataWithJSONObject:sortedManifest
                                                           options:kNilOptions
                                                             error:error];
    if (*error) {
        return nil;
    }

    NSString* manifestString = [[NSString alloc] initWithData:manifestData
                                                     encoding:NSUTF8StringEncoding];
    // The JSON serialization turns path separators into "\/", e.g. "www\/images\/image.png"
    manifestString = [manifestString stringByReplacingOccurrencesOfString:@"\\/"
                                                               withString:@"/"];
    return [self computeHashForData:[NSData dataWithBytes:manifestString.UTF8String length:manifestString.length]];
}

+ (NSString*)computeHashForData:(NSData*)inputData
{
    uint8_t digest[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(inputData.bytes, (CC_LONG)inputData.length, digest);
    NSMutableString* inputHash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
        [inputHash appendFormat:@"%02x", digest[i]];
    }

    return inputHash;
}

+ (NSString*)getBinaryHash:(NSError**)error
{
    return [self getHashForPath:[self binaryAssetsPath] error:error];
}

+ (NSString*)getHashForPath:(NSString*)path error:(NSError**)error
{
    NSMutableArray* manifestEntries = [NSMutableArray array];
    [self addFolderEntriesToManifest:path
                          pathPrefix:@"www"
                     manifestEntries:manifestEntries
                               error:error];
    return [self computeFinalHashFromManifestEntries:manifestEntries error:error];
}

@end
