import { encoding as textEncoding } from '../text';
import { getFileExtension, iOSNativeHelper } from '../utils';
// TODO: Implement all the APIs receiving callback using async blocks
// TODO: Check whether we need try/catch blocks for the iOS implementation
export class FileSystemAccess {
    constructor() {
        this.copy = this.copySync.bind(this);
        this.readText = this.readTextSync.bind(this);
        this.readBuffer = this.readBufferSync.bind(this);
        this.read = this.readSync.bind(this);
        this.writeText = this.writeTextSync.bind(this);
        this.appendBuffer = this.appendBufferSync.bind(this);
        this.append = this.appendSync.bind(this);
        this.appendText = this.appendTextSync.bind(this);
        this.writeBuffer = this.writeBufferSync.bind(this);
        this.write = this.writeSync.bind(this);
    }
    getLastModified(path) {
        const fileManager = NSFileManager.defaultManager;
        const attributes = fileManager.attributesOfItemAtPathError(path);
        if (attributes) {
            return attributes.objectForKey('NSFileModificationDate');
        }
        else {
            return new Date();
        }
    }
    getFileSize(path) {
        const fileManager = NSFileManager.defaultManager;
        const attributes = fileManager.attributesOfItemAtPathError(path);
        if (attributes) {
            return attributes.objectForKey('NSFileSize');
        }
        else {
            return 0;
        }
    }
    getParent(path, onError) {
        try {
            const fileManager = NSFileManager.defaultManager;
            const nsString = NSString.stringWithString(path);
            const parentPath = nsString.stringByDeletingLastPathComponent;
            const name = fileManager.displayNameAtPath(parentPath);
            return {
                path: parentPath.toString(),
                name: name,
            };
        }
        catch (exception) {
            if (onError) {
                onError(exception);
            }
            return undefined;
        }
    }
    getFile(path, onError) {
        try {
            const fileManager = NSFileManager.defaultManager;
            const exists = fileManager.fileExistsAtPath(path);
            if (!exists) {
                const parentPath = this.getParent(path, onError).path;
                if (!fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(parentPath, true, null) || !fileManager.createFileAtPathContentsAttributes(path, null, null)) {
                    if (onError) {
                        onError(new Error("Failed to create file at path '" + path + "'"));
                    }
                    return undefined;
                }
            }
            const fileName = fileManager.displayNameAtPath(path);
            return {
                path: path,
                name: fileName,
                extension: this.getFileExtension(path),
            };
        }
        catch (exception) {
            if (onError) {
                onError(exception);
            }
            return undefined;
        }
    }
    getFolder(path, onError) {
        try {
            const fileManager = NSFileManager.defaultManager;
            const exists = this.folderExists(path);
            if (!exists) {
                try {
                    fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(path, true, null);
                }
                catch (ex) {
                    if (onError) {
                        onError(new Error("Failed to create folder at path '" + path + "': " + ex));
                    }
                    return undefined;
                }
            }
            const dirName = fileManager.displayNameAtPath(path);
            return {
                path: path,
                name: dirName,
            };
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to create folder at path '" + path + "'"));
            }
            return undefined;
        }
    }
    getExistingFolder(path, onError) {
        try {
            const fileManager = NSFileManager.defaultManager;
            const exists = this.folderExists(path);
            if (exists) {
                const dirName = fileManager.displayNameAtPath(path);
                return {
                    path: path,
                    name: dirName,
                };
            }
            return undefined;
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to get folder at path '" + path + "'"));
            }
            return undefined;
        }
    }
    eachEntity(path, onEntity, onError) {
        if (!onEntity) {
            return;
        }
        this.enumEntities(path, onEntity, onError);
    }
    getEntities(path, onError) {
        const fileInfos = new Array();
        const onEntity = function (entity) {
            fileInfos.push(entity);
            return true;
        };
        let errorOccurred;
        const localError = function (error) {
            if (onError) {
                onError(error);
            }
            errorOccurred = true;
        };
        this.enumEntities(path, onEntity, localError);
        if (!errorOccurred) {
            return fileInfos;
        }
        return null;
    }
    fileExists(path) {
        const result = this.exists(path);
        return result.exists;
    }
    folderExists(path) {
        const result = this.exists(path);
        return result.exists && result.isDirectory;
    }
    exists(path) {
        const fileManager = NSFileManager.defaultManager;
        const isDirectory = new interop.Reference(interop.types.bool, false);
        const exists = fileManager.fileExistsAtPathIsDirectory(path, isDirectory);
        return { exists: exists, isDirectory: isDirectory.value };
    }
    concatPath(left, right) {
        return NSString.pathWithComponents([left, right]).toString();
    }
    deleteFile(path, onError) {
        this.deleteEntity(path, onError);
    }
    deleteFolder(path, onError) {
        this.deleteEntity(path, onError);
    }
    emptyFolder(path, onError) {
        const fileManager = NSFileManager.defaultManager;
        const entities = this.getEntities(path, onError);
        if (!entities) {
            return;
        }
        for (let i = 0; i < entities.length; i++) {
            try {
                fileManager.removeItemAtPathError(entities[i].path);
            }
            catch (ex) {
                if (onError) {
                    onError(new Error("Failed to empty folder '" + path + "': " + ex));
                }
                return;
            }
        }
    }
    rename(path, newPath, onError) {
        const fileManager = NSFileManager.defaultManager;
        try {
            fileManager.moveItemAtPathToPathError(path, newPath);
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to rename '" + path + "' to '" + newPath + "': " + ex));
            }
        }
    }
    getLogicalRootPath() {
        const mainBundlePath = NSBundle.mainBundle.bundlePath;
        const resolvedPath = NSString.stringWithString(mainBundlePath).stringByResolvingSymlinksInPath;
        return resolvedPath;
    }
    getDocumentsFolderPath() {
        return this.getKnownPath(9 /* NSSearchPathDirectory.DocumentDirectory */);
    }
    getExternalDocumentsFolderPath() {
        return this.getDocumentsFolderPath();
    }
    getTempFolderPath() {
        return this.getKnownPath(13 /* NSSearchPathDirectory.CachesDirectory */);
    }
    getCurrentAppPath() {
        return iOSNativeHelper.getCurrentAppPath();
    }
    copySync(src, dest, onError) {
        const fileManager = NSFileManager.defaultManager;
        try {
            return fileManager.copyItemAtPathToPathError(src, dest);
        }
        catch (error) {
            if (error.message.indexOf('exists') > -1) {
                // check the size of file if empty remove then try copying again
                // this could be zero due to using File.fromPath passing in a new file
                let didRemove = false;
                try {
                    didRemove = fileManager.removeItemAtPathError(dest);
                    return fileManager.copyItemAtPathToPathError(src, dest);
                }
                catch (exception) {
                    if (onError) {
                        if (didRemove) {
                            onError(error);
                        }
                        else {
                            onError(exception);
                        }
                    }
                }
            }
            if (onError) {
                onError(error);
            }
        }
        return false;
    }
    copyAsync(src, dest) {
        return new Promise((resolve, reject) => {
            try {
                NSData.dataWithContentsOfFileCompletion(src, (data) => {
                    if (!data) {
                        reject(new Error("Failed to read file at path '" + src));
                    }
                    else {
                        data.writeToFileAtomicallyCompletion(dest, true, () => {
                            if (this.fileExists(dest)) {
                                const size = this.getFileSize(dest);
                                if (size === data.length) {
                                    resolve(true);
                                }
                                else {
                                    reject(new Error("Failed to write file at path '" + dest));
                                }
                            }
                            else {
                                reject(new Error("Failed to write file at path '" + dest));
                            }
                        });
                    }
                });
            }
            catch (ex) {
                reject(ex);
            }
        });
    }
    readTextAsync(path, encoding) {
        const actualEncoding = encoding || textEncoding.UTF_8;
        return new Promise((resolve, reject) => {
            try {
                NSString.stringWithContentsOfFileEncodingCompletion(path, actualEncoding, (result, error) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        resolve(result.toString());
                    }
                });
            }
            catch (ex) {
                reject(new Error("Failed to read file at path '" + path + "': " + ex));
            }
        });
    }
    readTextSync(path, onError, encoding) {
        const actualEncoding = encoding || textEncoding.UTF_8;
        try {
            const nsString = NSString.stringWithContentsOfFileEncodingError(path, actualEncoding);
            return nsString.toString();
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to read file at path '" + path + "': " + ex));
            }
        }
    }
    readBufferAsync(path) {
        return new Promise((resolve, reject) => {
            try {
                NSData.dataWithContentsOfFileCompletion(path, (data) => {
                    resolve(interop.bufferFromData(data));
                });
            }
            catch (ex) {
                reject(new Error("Failed to read file at path '" + path + "': " + ex));
            }
        });
    }
    readBufferSync(path, onError) {
        try {
            return interop.bufferFromData(NSData.dataWithContentsOfFile(path));
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to read file at path '" + path + "': " + ex));
            }
        }
    }
    readAsync(path) {
        return new Promise((resolve, reject) => {
            try {
                NSData.dataWithContentsOfFileCompletion(path, resolve);
            }
            catch (ex) {
                reject(new Error("Failed to read file at path '" + path + "': " + ex));
            }
        });
    }
    readSync(path, onError) {
        try {
            return NSData.dataWithContentsOfFile(path);
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to read file at path '" + path + "': " + ex));
            }
        }
    }
    writeTextAsync(path, content, encoding) {
        const nsString = NSString.stringWithString(content);
        const actualEncoding = encoding || textEncoding.UTF_8;
        return new Promise((resolve, reject) => {
            try {
                nsString.writeToFileAtomicallyEncodingCompletion(path, true, actualEncoding, (error) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        resolve();
                    }
                });
            }
            catch (ex) {
                reject(new Error("Failed to write file at path '" + path + "': " + ex));
            }
        });
    }
    writeTextSync(path, content, onError, encoding) {
        const nsString = NSString.stringWithString(content);
        const actualEncoding = encoding || textEncoding.UTF_8;
        // TODO: verify the useAuxiliaryFile parameter should be false
        try {
            nsString.writeToFileAtomicallyEncodingError(path, false, actualEncoding);
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to write to file '" + path + "': " + ex));
            }
        }
    }
    static getBuffer(buffer) {
        if (buffer instanceof ArrayBuffer) {
            return NSData.dataWithData(buffer);
        }
        else {
            const buf = NSData.dataWithData(buffer?.buffer);
            const len = buffer.byteLength;
            return NSData.dataWithBytesNoCopyLength(buf.bytes.add(buffer?.byteOffset ?? 0), len);
        }
    }
    appendBufferAsync(path, content) {
        return new Promise((resolve, reject) => {
            try {
                const handle = NSFileHandle.fileHandleForWritingAtPath(path);
                handle.appendDataCompletion(FileSystemAccess.getBuffer(content), (error) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        resolve();
                    }
                    handle.closeFile();
                });
            }
            catch (ex) {
                reject(new Error("Failed to write file at path '" + path + "': " + ex));
            }
        });
    }
    appendBufferSync(path, content, onError) {
        try {
            const handle = NSFileHandle.fileHandleForWritingAtPath(path);
            handle.seekToEndOfFile();
            handle.writeData(FileSystemAccess.getBuffer(content));
            handle.closeFile();
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to write to file '" + path + "': " + ex));
            }
        }
    }
    appendAsync(path, content) {
        return new Promise((resolve, reject) => {
            try {
                const handle = NSFileHandle.fileHandleForWritingAtPath(path);
                handle.appendDataCompletion(content, (error) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        resolve();
                    }
                    handle.closeFile();
                });
            }
            catch (ex) {
                reject(new Error("Failed to write file at path '" + path + "': " + ex));
            }
        });
    }
    appendSync(path, content, onError) {
        try {
            const handle = NSFileHandle.fileHandleForWritingAtPath(path);
            handle.seekToEndOfFile();
            handle.writeData(content);
            handle.closeFile();
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to write to file '" + path + "': " + ex));
            }
        }
    }
    appendTextAsync(path, content, encoding) {
        const nsString = NSString.stringWithString(content);
        const actualEncoding = encoding || textEncoding.UTF_8;
        return new Promise((resolve, reject) => {
            try {
                const data = nsString.dataUsingEncoding(actualEncoding);
                const handle = NSFileHandle.fileHandleForWritingAtPath(path);
                handle.appendDataCompletion(data, (error) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        resolve();
                    }
                    handle.closeFile();
                });
            }
            catch (ex) {
                reject(new Error("Failed to append file at path '" + path + "': " + ex));
            }
        });
    }
    appendTextSync(path, content, onError, encoding) {
        const nsString = NSString.stringWithString(content);
        const actualEncoding = encoding || textEncoding.UTF_8;
        // TODO: verify the useAuxiliaryFile parameter should be false
        try {
            const data = nsString.dataUsingEncoding(actualEncoding);
            const handle = NSFileHandle.fileHandleForWritingAtPath(path);
            handle.seekToEndOfFile();
            handle.writeData(data);
            handle.closeFile();
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to append to file '" + path + "': " + ex));
            }
        }
    }
    writeBufferAsync(path, content) {
        return new Promise((resolve, reject) => {
            try {
                FileSystemAccess.getBuffer(content).writeToFileAtomicallyCompletion(path, true, () => {
                    resolve();
                });
            }
            catch (ex) {
                reject(new Error("Failed to write file at path '" + path + "': " + ex));
            }
        });
    }
    writeBufferSync(path, content, onError) {
        try {
            FileSystemAccess.getBuffer(content).writeToFileAtomically(path, true);
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to write to file '" + path + "': " + ex));
            }
        }
    }
    writeAsync(path, content) {
        return new Promise((resolve, reject) => {
            try {
                content.writeToFileAtomicallyCompletion(path, true, () => {
                    resolve();
                });
            }
            catch (ex) {
                reject(new Error("Failed to write file at path '" + path + "': " + ex));
            }
        });
    }
    writeSync(path, content, onError) {
        try {
            content.writeToFileAtomically(path, true);
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to write to file '" + path + "': " + ex));
            }
        }
    }
    getKnownPath(folderType) {
        const fileManager = NSFileManager.defaultManager;
        const paths = fileManager.URLsForDirectoryInDomains(folderType, 1 /* NSSearchPathDomainMask.UserDomainMask */);
        const url = paths.objectAtIndex(0);
        return url.path;
    }
    getFileExtension(path) {
        return getFileExtension(path);
    }
    deleteEntity(path, onError) {
        const fileManager = NSFileManager.defaultManager;
        try {
            fileManager.removeItemAtPathError(path);
        }
        catch (ex) {
            if (onError) {
                onError(new Error("Failed to delete file at path '" + path + "': " + ex));
            }
        }
    }
    enumEntities(path, callback, onError) {
        try {
            const fileManager = NSFileManager.defaultManager;
            let files;
            try {
                files = fileManager.contentsOfDirectoryAtPathError(path);
            }
            catch (ex) {
                if (onError) {
                    onError(new Error("Failed to enum files for folder '" + path + "': " + ex));
                }
                return;
            }
            for (let i = 0; i < files.count; i++) {
                const file = files.objectAtIndex(i);
                const info = {
                    path: this.concatPath(path, file),
                    name: file,
                    extension: '',
                };
                if (!this.folderExists(this.joinPath(path, file))) {
                    info.extension = this.getFileExtension(info.path);
                }
                const retVal = callback(info);
                if (retVal === false) {
                    // the callback returned false meaning we should stop the iteration
                    break;
                }
            }
        }
        catch (ex) {
            if (onError) {
                onError(ex);
            }
        }
    }
    getPathSeparator() {
        return '/';
    }
    normalizePath(path) {
        const nsString = NSString.stringWithString(path);
        const normalized = nsString.stringByStandardizingPath;
        return normalized;
    }
    joinPath(left, right) {
        const nsString = NSString.stringWithString(left);
        return nsString.stringByAppendingPathComponent(right);
    }
    joinPaths(paths) {
        return iOSNativeHelper.joinPaths(...paths);
    }
}
// stub to avoid cross platform warning
export class FileSystemAccess29 extends FileSystemAccess {
}
//# sourceMappingURL=file-system-access.ios.js.map