 //
 //  Data+Gzip.swift
 //
-//  Version 3.1.5
 
 /*
  The MIT License (MIT)
  
- © 2014-2017 1024jp <wolfrosch.com>
+ © 2014-2019 1024jp <wolfrosch.com>
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal

 */

import Foundation
import zlib

/**
 Compression level with constants based on the zlib's constants.
 */
typealias CompressionLevel = Int32
extension CompressionLevel {
    static let noCompression = Z_NO_COMPRESSION
    static let bestSpeed = Z_BEST_SPEED
    static let bestCompression = Z_BEST_COMPRESSION
    
    static let defaultCompression = Z_DEFAULT_COMPRESSION
}

/**
 Errors on gzipping/gunzipping based on the zlib error codes.
 */
enum GzipError: Error {
    // cf. http://www.zlib.net/manual.html
    
    /**
     The stream structure was inconsistent.
     
     - underlying zlib error: `Z_STREAM_ERROR` (-2)
     - parameter message: returned message by zlib
     */
    case stream(message: String)
    
    /**
     The input data was corrupted (input stream not conforming to the zlib format or incorrect check value).
     
     - underlying zlib error: `Z_DATA_ERROR` (-3)
     - parameter message: returned message by zlib
     */
    case data(message: String)
    
    /**
     There was not enough memory.
     
     - underlying zlib error: `Z_MEM_ERROR` (-4)
     - parameter message: returned message by zlib
     */
    case memory(message: String)
    
    /**
     No progress is possible or there was not enough room in the output buffer.
     
     - underlying zlib error: `Z_BUF_ERROR` (-5)
     - parameter message: returned message by zlib
     */
    case buffer(message: String)
    
    /**
     The zlib library version is incompatible with the version assumed by the caller.
     
     - underlying zlib error: `Z_VERSION_ERROR` (-6)
     - parameter message: returned message by zlib
     */
    case version(message: String)
    
    /**
     An unknown error occurred.
     
     - parameter message: returned message by zlib
     - parameter code: return error by zlib
     */
    case unknown(message: String, code: Int)
    
    internal init(code: Int32, msg: UnsafePointer<CChar>?) {
        let message: String = {
            guard let msg = msg, let message = String(validatingUTF8: msg) else {
                return "Unknown gzip error"
            }
            return message
        }()
        
        self = {
            switch code {
            case Z_STREAM_ERROR:
                return .stream(message: message)
                
            case Z_DATA_ERROR:
                return .data(message: message)
                
            case Z_MEM_ERROR:
                return .memory(message: message)
                
            case Z_BUF_ERROR:
                return .buffer(message: message)
                
            case Z_VERSION_ERROR:
                return .version(message: message)
                
            default:
                return .unknown(message: message, code: Int(code))
            }
        }()
    }
    
    var localizedDescription: String {
        let description: String = {
            switch self {
            case .stream(let message):
                return message
            case .data(let message):
                return message
            case .memory(let message):
                return message
            case .buffer(let message):
                return message
            case .version(let message):
                return message
            case .unknown(let message, _):
                return message
            }
        }()
        
        return description
    }
}

extension Data {
    /**
     Whether the data is compressed in gzip format.
     */
    var isGzipped: Bool {
        return self.starts(with: [0x1f, 0x8b])  // check magic number
    }
    
    /**
     Create a new `Data` object by compressing the receiver using zlib.
     Throws an error if compression failed.
     
     - parameters:
     - level: Compression level in the range of `0` (no compression) to `9` (maximum compression).
     
     - throws: `GzipError`
     - returns: Gzip-compressed `Data` object.
     */
    func gzipped(level: CompressionLevel = .defaultCompression) throws -> Data {
        guard !self.isEmpty else {
            return Data()
        }
        
        var stream = self.createZStream()
        var status: Int32
        
        status = deflateInit2_(&stream, level, Z_DEFLATED, MAX_WBITS + 16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY, ZLIB_VERSION, Int32(DataSize.stream))
        
        guard status == Z_OK else {
            // deflateInit2 returns:

        
        return stream
    }
    
}


private struct DataSize {
    
    static let chunk = 2 ^ 14
    static let stream = MemoryLayout<z_stream>.size
    
