"use strict";
/**
 * Copyright 2020 Baidu Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-console */
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const querystring_1 = __importDefault(require("querystring"));
const apiUrl = '/api';
function isEmpty(obj) {
    if (obj == null) {
        return true;
    }
    return !Object.keys(obj).length;
}
class IO {
    constructor(url, dataDir) {
        this.metadata = [];
        this.url = url;
        this.dataDir = dataDir;
        fs_1.writeFileSync(path_1.default.join(this.dataDir, IO.metaFileName), JSON.stringify(this.metadata), {
            encoding: 'utf-8',
            flag: 'w'
        });
    }
    static isSameUri(url1, url2) {
        if (url1.uri !== url2.uri) {
            return false;
        }
        if (!isEmpty(url2.query)) {
            if (isEmpty(url1.query)) {
                return false;
            }
            for (const [key, value] of Object.entries(url2.query)) {
                const existValue = url1.query[key];
                if (existValue !== value) {
                    if (Array.isArray(value) && Array.isArray(existValue)) {
                        const count = value.reduce((m, v) => {
                            if (m[v] == null) {
                                m[v] = 1;
                            }
                            else {
                                m[v]++;
                            }
                            return m;
                        }, {});
                        for (const i of existValue) {
                            if (count[i] == null) {
                                return false;
                            }
                            count[i]--;
                        }
                        return Object.values(count).every(c => c === 0);
                    }
                    return false;
                }
            }
            return true;
        }
        else {
            return isEmpty(url1.query);
        }
    }
    generateFilename(content) {
        const hash = crypto_1.default.createHash(IO.hashFunction);
        hash.update(content);
        return hash.digest('hex');
    }
    addMeta(meta) {
        const exist = this.metadata.find(data => IO.isSameUri(data, meta));
        if (!exist) {
            this.metadata.push(meta);
        }
    }
    async write(filePath, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    content, contentType, options) {
        var _a;
        const { default: mkdirp } = await Promise.resolve().then(() => __importStar(require('mkdirp')));
        const type = 'string' === typeof options ? options : (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'json';
        const fileDir = path_1.default.join(this.dataDir, IO.dataPath, filePath);
        await mkdirp(fileDir);
        let fileContent;
        let extname;
        if (type === 'buffer') {
            const { default: mime } = await Promise.resolve().then(() => __importStar(require('mime-types')));
            extname = mime.extension(contentType) || '';
            if (extname) {
                extname = '.' + extname;
            }
            fileContent = content;
        }
        else {
            extname = '.json';
            fileContent = Buffer.from(JSON.stringify(content), 'utf-8');
        }
        const filename = this.generateFilename(fileContent) + extname;
        await fs_1.promises.writeFile(path_1.default.join(fileDir, filename), fileContent, {
            encoding: null,
            flag: 'w'
        });
        console.log(`write file ${path_1.default.join(filePath, filename)}`);
        return filename;
    }
    async fetch(uri, query) {
        const { default: fetch } = await Promise.resolve().then(() => __importStar(require('node-fetch')));
        let url = this.url + apiUrl + uri;
        if (!isEmpty(query)) {
            url += '?' + querystring_1.default.stringify(query);
        }
        try {
            return await fetch(url);
        }
        catch (e) {
            console.error(e);
        }
    }
    async fetchAndWrite(uri, query, options) {
        var _a;
        const type = 'string' === typeof options ? options : (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'json';
        const response = await this.fetch(uri, query);
        if (!response.ok) {
            throw new Error(`not ok: ${uri}`);
        }
        let content;
        if (type === 'buffer') {
            content = await response.buffer();
        }
        else {
            content = (await response.json());
        }
        const filename = await this.write(uri, content, response.headers.get('content-type'), options);
        this.addMeta({
            uri,
            query: isEmpty(query) ? undefined : querystring_1.default.parse(querystring_1.default.stringify(query)),
            filename,
            headers: ['Content-Type', 'Content-Disposition'].reduce((m, t) => {
                m[t] = response.headers.get(t) || undefined;
                return m;
            }, {})
        });
        return content;
    }
    async save(uri, query) {
        return (await this.fetchAndWrite(uri, query, 'json')).data;
    }
    async saveBinary(uri, query) {
        return (await this.fetchAndWrite(uri, query, 'buffer'));
    }
    async getData(uri, query) {
        return (await (await this.fetch(uri, query)).json()).data;
    }
    generateMeta() {
        return fs_1.promises.writeFile(path_1.default.join(this.dataDir, IO.metaFileName), JSON.stringify(this.metadata), {
            encoding: 'utf-8',
            flag: 'w'
        });
    }
    sleep(time) {
        return new Promise(resolve => {
            setTimeout(resolve, time);
        });
    }
}
exports.default = IO;
IO.metaFileName = 'meta.json';
IO.dataPath = 'data';
IO.hashFunction = 'md4';
