#import <Foundation/Foundation.h>
#import "DDLog.h"

@class DDLogFileInfo;

/**
 * Welcome to Cocoa Lumberjack!
 * 
 * The project page has a wealth of documentation if you have any questions.
 * https://github.com/CocoaLumberjack/CocoaLumberjack
 * 
 * If you're new to the project you may wish to read the "Getting Started" wiki.
 * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted
 * 
 * 
 * This class provides a logger to write log statements to a file.
**/


// Default configuration and safety/sanity values.
// 
// maximumFileSize         -> DEFAULT_LOG_MAX_FILE_SIZE
// rollingFrequency        -> DEFAULT_LOG_ROLLING_FREQUENCY
// maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES
// logFilesDiskQuota       -> DEFAULT_LOG_FILES_DISK_QUOTA
// 
// You should carefully consider the proper configuration values for your application.

#define DEFAULT_LOG_MAX_FILE_SIZE     (1024 * 1024)      //  1 MB
#define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24)     // 24 Hours
#define DEFAULT_LOG_MAX_NUM_LOG_FILES (5)                //  5 Files
#define DEFAULT_LOG_FILES_DISK_QUOTA  (20 * 1024 * 1024) // 20 MB


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// The LogFileManager protocol is designed to allow you to control all aspects of your log files.
// 
// The primary purpose of this is to allow you to do something with the log files after they have been rolled.
// Perhaps you want to compress them to save disk space.
// Perhaps you want to upload them to an FTP server.
// Perhaps you want to run some analytics on the file.
// 
// A default LogFileManager is, of course, provided.
// The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
// 
// This protocol provides various methods to fetch the list of log files.
// 
// There are two variants: sorted and unsorted.
// If sorting is not necessary, the unsorted variant is obviously faster.
// The sorted variant will return an array sorted by when the log files were created,
// with the most recently created log file at index 0, and the oldest log file at the end of the array.
// 
// You can fetch only the log file paths (full path including name), log file names (name only),
// or an array of DDLogFileInfo objects.
// The DDLogFileInfo class is documented below, and provides a handy wrapper that
// gives you easy access to various file attributes such as the creation date or the file size.

@protocol DDLogFileManager <NSObject>
@required

// Public properties

/**
 * The maximum number of archived log files to keep on disk.
 * For example, if this property is set to 3,
 * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk.
 * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted.
 * 
 * You may optionally disable this option by setting it to zero.
**/
@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;

/**
 * The maximum space that logs can take. On rolling logfile all old logfiles that exceed logFilesDiskQuota will
 * be deleted.
 *
 * You may optionally disable this option by setting it to zero.
**/
@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota;

// Public methods

- (NSString *)logsDirectory;

- (NSArray *)unsortedLogFilePaths;
- (NSArray *)unsortedLogFileNames;
- (NSArray *)unsortedLogFileInfos;

- (NSArray *)sortedLogFilePaths;
- (NSArray *)sortedLogFileNames;
- (NSArray *)sortedLogFileInfos;

// Private methods (only to be used by DDFileLogger)

- (NSString *)createNewLogFile;

@optional

// Notifications from DDFileLogger

- (void)didArchiveLogFile:(NSString *)logFilePath;
- (void)didRollAndArchiveLogFile:(NSString *)logFilePath;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Default log file manager.
 * 
 * All log files are placed inside the logsDirectory.
 * If a specific logsDirectory isn't specified, the default directory is used.
 * On Mac, this is in ~/Library/Logs/<Application Name>.
 * On iPhone, this is in ~/Library/Caches/Logs.
 * 
 * Log files are named "<bundle identifier> <date> <time>.log"
 * Example: com.organization.myapp 2013-12-03 17-14.log
 * 
 * Archived log files are automatically deleted according to the maximumNumberOfLogFiles property.
**/
@interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
{
    NSUInteger maximumNumberOfLogFiles;
    unsigned long long logFilesDiskQuota;
    NSString *_logsDirectory;
#if TARGET_OS_IPHONE
    NSString* _defaultFileProtectionLevel;
#endif
}

- (id)init;
- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory;
#if TARGET_OS_IPHONE
/*
 * Calling this constructor you can override the default "automagically" chosen NSFileProtection level.
 * Useful if you are writing a command line utility / CydiaSubstrate addon for iOS that has no NSBundle
 * or like SpringBoard no BackgroundModes key in the NSBundle:
 *    iPhone:~ root# cycript -p SpringBoard
 *    cy# [NSBundle mainBundle]
 *    #"NSBundle </System/Library/CoreServices/SpringBoard.app> (loaded)"
 *    cy# [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
 *    null
 *    cy#
 **/
- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory defaultFileProtectionLevel:(NSString*)fileProtectionLevel;
#endif

/*
 * Methods to override.
 *
 * Log files are named "<bundle identifier> <date> <time>.log"
 * Example: com.organization.myapp 2013-12-03 17-14.log
 *
 * If you wish to change default filename, you can override following two methods.
 * - newLogFileName method would be called on new logfile creation.
 * - isLogFile: method would be called to filter logfiles from all other files in logsDirectory.
 *   You have to parse given filename and return YES if it is logFile.
 *
 * **NOTE**
 * newLogFileName returns filename. If appropriate file already exists, number would be added
 * to filename before extension. You have to handle this case in isLogFile: method.
 *
 * Example:
 * - newLogFileName returns "com.organization.myapp 2013-12-03.log",
 *   file "com.organization.myapp 2013-12-03.log" would be created.
 * - after some time "com.organization.myapp 2013-12-03.log" is archived
 * - newLogFileName again returns "com.organization.myapp 2013-12-03.log",
 *   file "com.organization.myapp 2013-12-03 2.log" would be created.
 * - after some time "com.organization.myapp 2013-12-03 1.log" is archived
 * - newLogFileName again returns "com.organization.myapp 2013-12-03.log",
 *   file "com.organization.myapp 2013-12-03 3.log" would be created.
**/
- (NSString *)newLogFileName;
- (BOOL)isLogFile:(NSString *)fileName;

/* Inherited from DDLogFileManager protocol:

@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
@property (readwrite, assign, atomic) NSUInteger logFilesDiskQuota;

- (NSString *)logsDirectory;

- (NSArray *)unsortedLogFilePaths;
- (NSArray *)unsortedLogFileNames;
- (NSArray *)unsortedLogFileInfos;

- (NSArray *)sortedLogFilePaths;
- (NSArray *)sortedLogFileNames;
- (NSArray *)sortedLogFileInfos;

*/

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Most users will want file log messages to be prepended with the date and time.
 * Rather than forcing the majority of users to write their own formatter,
 * we will supply a logical default formatter.
 * Users can easily replace this formatter with their own by invoking the setLogFormatter method.
 * It can also be removed by calling setLogFormatter, and passing a nil parameter.
 * 
 * In addition to the convenience of having a logical default formatter,
 * it will also provide a template that makes it easy for developers to copy and change.
**/
@interface DDLogFileFormatterDefault : NSObject <DDLogFormatter>
{
    NSDateFormatter *dateFormatter;
}

- (id)init;
- (instancetype)initWithDateFormatter:(NSDateFormatter *)dateFormatter;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface DDFileLogger : DDAbstractLogger <DDLogger>
{
    __strong id <DDLogFileManager> logFileManager;
    
    DDLogFileInfo *currentLogFileInfo;
    NSFileHandle *currentLogFileHandle;
    
    dispatch_source_t currentLogFileVnode;
    dispatch_source_t rollingTimer;
    
    unsigned long long maximumFileSize;
    NSTimeInterval rollingFrequency;
}

- (id)init;
- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)logFileManager;

/**
 * Log File Rolling:
 * 
 * maximumFileSize:
 *   The approximate maximum size to allow log files to grow.
 *   If a log file is larger than this value after a log statement is appended,
 *   then the log file is rolled.
 * 
 * rollingFrequency
 *   How often to roll the log file.
 *   The frequency is given as an NSTimeInterval, which is a double that specifies the interval in seconds.
 *   Once the log file gets to be this old, it is rolled.
 * 
 * Both the maximumFileSize and the rollingFrequency are used to manage rolling.
 * Whichever occurs first will cause the log file to be rolled.
 * 
 * For example:
 * The rollingFrequency is 24 hours,
 * but the log file surpasses the maximumFileSize after only 20 hours.
 * The log file will be rolled at that 20 hour mark.
 * A new log file will be created, and the 24 hour timer will be restarted.
 * 
 * You may optionally disable rolling due to filesize by setting maximumFileSize to zero.
 * If you do so, rolling is based solely on rollingFrequency.
 * 
 * You may optionally disable rolling due to time by setting rollingFrequency to zero (or any non-positive number).
 * If you do so, rolling is based solely on maximumFileSize.
 * 
 * If you disable both maximumFileSize and rollingFrequency, then the log file won't ever be rolled.
 * This is strongly discouraged.
**/
@property (readwrite, assign) unsigned long long maximumFileSize;
@property (readwrite, assign) NSTimeInterval rollingFrequency;
@property (readwrite, assign, atomic) BOOL doNotReuseLogFiles;

/**
 * The DDLogFileManager instance can be used to retrieve the list of log files,
 * and configure the maximum number of archived log files to keep.
 * 
 * @see DDLogFileManager.maximumNumberOfLogFiles
**/
@property (strong, nonatomic, readonly) id <DDLogFileManager> logFileManager;

/**
 * When using a custom formatter you can set the logMessage method not to append
 * '\n' character after each output. This allows for some greater flexibility with
 * custom formatters. Default value is YES.
**/
 
@property (readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;

// You can optionally force the current log file to be rolled with this method.
// CompletionBlock will be called on main queue.

- (void)rollLogFileWithCompletionBlock:(void (^)())completionBlock;

// Method is deprecated. Use rollLogFileWithCompletionBlock: method instead.

- (void)rollLogFile __attribute((deprecated));

// Inherited from DDAbstractLogger

// - (id <DDLogFormatter>)logFormatter;
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * DDLogFileInfo is a simple class that provides access to various file attributes.
 * It provides good performance as it only fetches the information if requested,
 * and it caches the information to prevent duplicate fetches.
 * 
 * It was designed to provide quick snapshots of the current state of log files,
 * and to help sort log files in an array.
 * 
 * This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
 * This is not what the class was designed for.
 * 
 * If you absolutely must get updated values,
 * you can invoke the reset method which will clear the cache.
**/
@interface DDLogFileInfo : NSObject
{
    __strong NSString *filePath;
    __strong NSString *fileName;
    
    __strong NSDictionary *fileAttributes;
    
    __strong NSDate *creationDate;
    __strong NSDate *modificationDate;
    
    unsigned long long fileSize;
}

@property (strong, nonatomic, readonly) NSString *filePath;
@property (strong, nonatomic, readonly) NSString *fileName;

@property (strong, nonatomic, readonly) NSDictionary *fileAttributes;

@property (strong, nonatomic, readonly) NSDate *creationDate;
@property (strong, nonatomic, readonly) NSDate *modificationDate;

@property (nonatomic, readonly) unsigned long long fileSize;

@property (nonatomic, readonly) NSTimeInterval age;

@property (nonatomic, readwrite) BOOL isArchived;

+ (instancetype)logFileWithPath:(NSString *)filePath;

- (instancetype)initWithFilePath:(NSString *)filePath;

- (void)reset;
- (void)renameFile:(NSString *)newFileName;

#if TARGET_IPHONE_SIMULATOR

// So here's the situation.
// Extended attributes are perfect for what we're trying to do here (marking files as archived).
// This is exactly what extended attributes were designed for.
// 
// But Apple screws us over on the simulator.
// Everytime you build-and-go, they copy the application into a new folder on the hard drive,
// and as part of the process they strip extended attributes from our log files.
// Normally, a copy of a file preserves extended attributes.
// So obviously Apple has gone to great lengths to piss us off.
// 
// Thus we use a slightly different tactic for marking log files as archived in the simulator.
// That way it "just works" and there's no confusion when testing.
// 
// The difference in method names is indicative of the difference in functionality.
// On the simulator we add an attribute by appending a filename extension.
// 
// For example:
// "mylog.txt" -> "mylog.archived.txt"
// "mylog"     -> "mylog.archived"

- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;

- (void)addExtensionAttributeWithName:(NSString *)attrName;
- (void)removeExtensionAttributeWithName:(NSString *)attrName;

#else

// Normal use of extended attributes used everywhere else,
// such as on Macs and on iPhone devices.

- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;

- (void)addExtendedAttributeWithName:(NSString *)attrName;
- (void)removeExtendedAttributeWithName:(NSString *)attrName;

#endif

- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;

@end
