"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments)).next());
    });
};
const iOSProxyServices = require("./ios-proxy-services");
const path = require("path");
const ref = require("ref");
const util = require("util");
class IOSDeviceFileSystem {
    constructor(device, devicePointer, $coreFoundation, $errors, $fs, $injector, $logger, $mobileDevice, $options) {
        this.device = device;
        this.devicePointer = devicePointer;
        this.$coreFoundation = $coreFoundation;
        this.$errors = $errors;
        this.$fs = $fs;
        this.$injector = $injector;
        this.$logger = $logger;
        this.$mobileDevice = $mobileDevice;
        this.$options = $options;
    }
    listFiles(devicePath, appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!devicePath) {
                devicePath = ".";
            }
            this.$logger.info("Listing %s", devicePath);
            let afcClient = this.resolveAfc();
            let walk = (root, indent) => {
                this.$logger.info(util.format("%s %s", Array(indent).join(" "), root));
                let children = [];
                try {
                    children = afcClient.listDir(root);
                }
                catch (e) {
                    children = [];
                }
                _.each(children, (child) => {
                    walk(root + "/" + child, indent + 1);
                });
            };
            walk(devicePath, 0);
        });
    }
    getFile(deviceFilePath, appIdentifier, outputFilePath) {
        return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
            let houseArrestClient;
            try {
                houseArrestClient = this.$injector.resolve(iOSProxyServices.HouseArrestClient, { device: this.device });
                let afcClient = yield this.getAfcClient(houseArrestClient, deviceFilePath, appIdentifier);
                let fileToRead = afcClient.open(deviceFilePath, "r");
                let fileToWrite = outputFilePath ? this.$fs.createWriteStream(outputFilePath) : process.stdout;
                if (outputFilePath) {
                    fileToWrite.on("close", () => {
                        houseArrestClient.closeSocket();
                        resolve();
                    });
                }
                let dataSizeToRead = 8192;
                let size = 0;
                while (true) {
                    let data = fileToRead.read(dataSizeToRead);
                    if (!data || data.length === 0) {
                        break;
                    }
                    fileToWrite.write(data);
                    size += data.length;
                }
                fileToRead.close();
                if (outputFilePath) {
                    fileToWrite.end();
                }
                this.$logger.trace("%s bytes read from %s", size.toString(), deviceFilePath);
            }
            catch (err) {
                if (houseArrestClient) {
                    houseArrestClient.closeSocket();
                }
                this.$logger.trace("Error while getting file from device", err);
                reject(err);
            }
        }));
    }
    putFile(localFilePath, deviceFilePath, appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            let houseArrestClient = this.$injector.resolve(iOSProxyServices.HouseArrestClient, { device: this.device });
            let afcClient = yield this.getAfcClient(houseArrestClient, deviceFilePath, appIdentifier);
            yield afcClient.transfer(path.resolve(localFilePath), deviceFilePath);
            houseArrestClient.closeSocket();
        });
    }
    deleteFile(deviceFilePath, appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            let houseArrestClient = this.$injector.resolve(iOSProxyServices.HouseArrestClient, { device: this.device });
            let afcClient = yield this.getAfcClient(houseArrestClient, deviceFilePath, appIdentifier);
            afcClient.deleteFile(deviceFilePath);
            houseArrestClient.closeSocket();
        });
    }
    transferFiles(deviceAppData, localToDevicePaths) {
        return __awaiter(this, void 0, void 0, function* () {
            let houseArrestClient = this.$injector.resolve(iOSProxyServices.HouseArrestClient, { device: this.device });
            let afcClient = yield this.getAfcClient(houseArrestClient, yield deviceAppData.getDeviceProjectRootPath(), deviceAppData.appIdentifier);
            yield Promise.all(_.map(localToDevicePaths, (localToDevicePathData) => __awaiter(this, void 0, void 0, function* () {
                let stats = this.$fs.getFsStats(localToDevicePathData.getLocalPath());
                if (stats.isFile()) {
                    yield afcClient.transfer(localToDevicePathData.getLocalPath(), localToDevicePathData.getDevicePath());
                }
            })));
            houseArrestClient.closeSocket();
        });
    }
    transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.transferFiles(deviceAppData, localToDevicePaths);
        });
    }
    getAfcClient(houseArrestClient, rootPath, appIdentifier) {
        if (rootPath.indexOf("/Documents/") === 0) {
            return houseArrestClient.getAfcClientForAppDocuments(appIdentifier);
        }
        return houseArrestClient.getAfcClientForAppContainer(appIdentifier);
    }
    resolveAfc() {
        let service = this.$options.app ? this.startHouseArrestService(this.$options.app) : this.device.startService(iOSProxyServices.MobileServices.APPLE_FILE_CONNECTION);
        let afcClient = this.$injector.resolve(iOSProxyServices.AfcClient, { service: service });
        return afcClient;
    }
    startHouseArrestService(bundleId) {
        let func = () => {
            let fdRef = ref.alloc("int");
            let result = this.$mobileDevice.deviceStartHouseArrestService(this.devicePointer, this.$coreFoundation.createCFString(bundleId), null, fdRef);
            let fd = fdRef.deref();
            if (result !== 0) {
                this.$errors.fail("AMDeviceStartHouseArrestService returned %s", result);
            }
            return fd;
        };
        return this.device.tryExecuteFunction(func);
    }
}
exports.IOSDeviceFileSystem = IOSDeviceFileSystem;
