"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProfileRepository = void 0;
/*
 *
 * Copyright 2018-present NEM
 *
 * 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.
 *
 */
const fs = require("fs");
const profile_migrations_1 = require("../migrations/profile.migrations");
const hdProfile_model_1 = require("../models/hdProfile.model");
const ledgerProfile_model_1 = require("../models/ledgerProfile.model");
const privateKeyProfile_model_1 = require("../models/privateKeyProfile.model");
const profile_model_1 = require("../models/profile.model");
/**
 * Profile repository
 */
class ProfileRepository {
    /**
     * Constructor
     * @param {string} fileUrl - File path where profiles are saved.
     */
    constructor(fileUrl) {
        this.fileUrl = fileUrl;
        this.filePath = require('os').homedir() + '/' + fileUrl;
    }
    /**
     * Find profile by name.
     * @param {string} name - Profile name.
     * @throws {Error}
     * @returns {Profile}
     */
    find(name) {
        const profiles = this.getProfiles();
        if (profiles[name]) {
            return this.createProfileFromDTO(profiles[name]);
        }
        throw new Error(`${name} not found`);
    }
    /**
     * Gets all profiles.
     * @returns {Profile[]}
     */
    all() {
        const profiles = this.getProfiles();
        const list = [];
        for (const name in profiles) {
            if (profiles.hasOwnProperty(name)) {
                list.push(this.createProfileFromDTO(profiles[name]));
            }
        }
        return list;
    }
    /**
     * Persists a profile to the storage
     * @param {Profile} profile
     * @returns {Profile}
     */
    save(profile) {
        const profiles = this.getProfiles();
        const { name } = profile.simpleWallet;
        if (profiles.hasOwnProperty(name)) {
            throw new Error(`A profile named ${name} already exists.`);
        }
        profiles[name] = profile.toDTO();
        this.saveProfiles(profiles);
        return profile;
    }
    /**
     * Sets a profile as the default one.
     * @param {string} name - Profile name.
     * @throws {Error}
     */
    setDefault(name) {
        const profiles = this.getProfiles();
        if (profiles.hasOwnProperty(name)) {
            for (const item in profiles) {
                if (item !== name) {
                    profiles[item].default = '0';
                }
                else {
                    profiles[item].default = '1';
                }
            }
            this.saveProfiles(profiles);
        }
        else {
            throw new Error(`${name} not found`);
        }
    }
    /**
     * Gets the default profile.
     * @returns {Profile}
     */
    getDefaultProfile() {
        const profiles = this.getProfiles();
        let defaultProfile = '';
        for (const name in profiles) {
            if ('1' === profiles[name].default) {
                defaultProfile = name;
            }
        }
        if ('' === defaultProfile) {
            throw new Error('default profile not found');
        }
        return this.find(defaultProfile);
    }
    /**
     * Get all profiles as JSON objects.
     * @returns {object}
     */
    getProfiles() {
        // check if profile storage file exists, creates one if not
        try {
            if (!fs.existsSync(this.filePath)) {
                fs.writeFileSync(this.filePath, '{}', 'utf-8');
                return {};
            }
        }
        catch (error) {
            throw new Error('Something went wrong when creating a file to store your profiles...' + JSON.stringify(error));
        }
        try {
            // get accounts from storage
            const profiles = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
            // migrate accounts if necessary
            if (!this.checkSchemaVersion(profiles)) {
                const migratedProfiles = this.migrate(profiles);
                // persist updated profiles
                this.saveProfiles(migratedProfiles);
                return migratedProfiles;
            }
            return profiles;
        }
        catch (error) {
            throw new Error('Something went wrong when retrieving your profiles from the storage...' + JSON.stringify(error));
        }
    }
    /**
     * Iterates entities to find *out-of-date* data schemas.
     * @param {ProfileRecord} profiles
     * @return {boolean}  True if profiles are up to date, false if any requires migration
     */
    checkSchemaVersion(profiles) {
        const profileNeedingMigration = Object.values(profiles).find(({ version }) => !version || version < profile_model_1.CURRENT_PROFILE_VERSION);
        return profileNeedingMigration === undefined;
    }
    /**
     * Return a profile record with updated profiles
     * @private
     * @param {ProfileRecord} profiles
     * @returns {ProfileRecord}
     */
    migrate(profiles) {
        const allMigrations = profile_migrations_1.ProfileMigrations.get();
        return Object.entries(profiles)
            .map((entry) => {
            const [name, profile] = entry;
            const profileVersion = Number(profile.version) || 0;
            // Skip if profile is up-to-date
            if (profileVersion >= profile_model_1.CURRENT_PROFILE_VERSION) {
                return { [name]: profile };
            }
            // get migrations to apply
            const migrations = Object.entries(allMigrations)
                .filter(([version]) => Number(version) > profileVersion)
                .map(([, migration]) => migration);
            // apply migrations
            let migrated = { [name]: profile };
            migrations.forEach((migration) => {
                migrated = migration(migrated);
            });
            return migrated;
        })
            .reduce((acc, profile) => ({ ...acc, ...profile }), {});
    }
    /**
     * Save profiles from JSON.
     * @param {JSON} profiles
     */
    saveProfiles(profiles) {
        try {
            fs.writeFileSync(this.filePath, JSON.stringify(profiles), 'utf-8');
            fs.chmodSync(this.filePath, 0o600);
        }
        catch (e) {
            throw new Error('Something went wrong when saving your profiles...' + JSON.stringify(e));
        }
    }
    /**
     * Creates a new profile from a DTO
     * @private
     * @param {ProfileDTO} args
     * @returns {Profile}
     */
    createProfileFromDTO(args) {
        if (args.type == 'HD') {
            return hdProfile_model_1.HdProfile.createFromDTO(args);
        }
        if (args.type == 'PrivateKey') {
            return privateKeyProfile_model_1.PrivateKeyProfile.createFromDTO(args);
        }
        if (args.type == 'Ledger') {
            return ledgerProfile_model_1.LedgerProfile.createFromDTO(args);
        }
        throw new Error(`Invalid profile type ${args.type}`);
    }
}
exports.ProfileRepository = ProfileRepository;
