/*
 * Copyright (c) Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#pragma once
#include <stratosphere/ncm/ncm_install_task_data.hpp>
#include <stratosphere/ncm/ncm_install_task_occupied_size.hpp>

namespace ams::ncm {

    enum class ListContentMetaKeyFilter : u8 {
        All          = 0,
        Committed    = 1,
        NotCommitted = 2,
    };

    enum InstallConfig {
        InstallConfig_None                   = (0 << 0),
        InstallConfig_SystemUpdate           = (1 << 2),
        InstallConfig_RequiresExFatDriver    = (1 << 3),
        InstallConfig_IgnoreTicket           = (1 << 4),
    };

    struct InstallThroughput {
        s64 installed;
        TimeSpan elapsed_time;
    };

    struct InstallContentMetaInfo {
        ContentId content_id;
        s64 content_size;
        ContentMetaKey key;
        bool verify_digest;
        bool has_key;
        Digest digest;

        static constexpr InstallContentMetaInfo MakeVerifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky, const Digest &d) {
            return {
                .content_id    = cid,
                .content_size  = sz,
                .key           = ky,
                .verify_digest = true,
                .has_key       = true,
                .digest        = d,
            };
        }

        static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky) {
            return {
                .content_id    = cid,
                .content_size  = sz,
                .key           = ky,
                .verify_digest = false,
                .has_key       = true,
            };
        }

        static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz) {
            return {
                .content_id    = cid,
                .content_size  = sz,
                .verify_digest = false,
                .has_key       = false,
            };
        }
    };

    static_assert(sizeof(InstallContentMetaInfo) == 0x50);

    class InstallTaskBase {
        NON_COPYABLE(InstallTaskBase);
        NON_MOVEABLE(InstallTaskBase);
        private:
            crypto::Sha256Generator m_sha256_generator{};
            StorageId m_install_storage{};
            InstallTaskDataBase *m_data{};
            InstallProgress m_progress{};
            os::SdkMutex m_progress_mutex{};
            u32 m_config{};
            os::SdkMutex m_cancel_mutex{};
            bool m_cancel_requested{};
            InstallThroughput m_throughput{};
            TimeSpan m_throughput_start_time{};
            os::SdkMutex m_throughput_mutex{};
            FirmwareVariationId m_firmware_variation_id{};
        private:
            ALWAYS_INLINE Result SetLastResultOnFailure(Result result) {
                if (R_FAILED(result)) {
                    this->SetLastResult(result);
                }
                R_RETURN(result);
            }
        public:
            InstallTaskBase() : m_data(), m_progress(), m_progress_mutex(), m_cancel_mutex(), m_cancel_requested(), m_throughput_mutex() { /* ... */ }
            virtual ~InstallTaskBase() { /* ... */ };
        public:
            virtual void Cancel();
            virtual void ResetCancel();

            Result Prepare();
            Result GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type);
            Result CalculateRequiredSize(s64 *out_size);
            Result Cleanup();
            Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter);
            Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset) { R_RETURN(this->ListContentMetaKey(out_keys_written, out_keys, out_keys_count, offset, ListContentMetaKeyFilter::All)); }
            Result ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset);
            Result Execute();
            Result PrepareAndExecute();
            Result Commit(const StorageContentMetaKey *keys, s32 num_keys);
            Result Commit() { R_RETURN(this->Commit(nullptr, 0)); }
            virtual InstallProgress GetProgress();
            void ResetLastResult();
            Result IncludesExFatDriver(bool *out);
            InstallThroughput GetThroughput();
            Result CalculateContentsSize(s64 *out_size, const ContentMetaKey &key, StorageId storage_id);
            Result ListOccupiedSize(s32 *out_written, InstallTaskOccupiedSize *out_list, s32 out_list_size, s32 offset);

            Result FindMaxRequiredApplicationVersion(u32 *out);
            Result FindMaxRequiredSystemVersion(u32 *out);
        protected:
            Result Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config);

            Result PrepareContentMeta(const InstallContentMetaInfo &meta_info, util::optional<ContentMetaKey> key, util::optional<u32> source_version);
            Result PrepareContentMeta(ContentId content_id, s64 size, ContentMetaType meta_type, AutoBuffer *buffer);
            Result WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size);
            void PrepareAgain();

            Result CountInstallContentMetaData(s32 *out_count);
            Result GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index);
            Result DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys);
            virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) = 0;

            virtual Result PrepareDependency();
            Result PrepareSystemUpdateDependency();
            virtual Result PrepareContentMetaIfLatest(const ContentMetaKey &key); /* NOTE: This is not virtual in Nintendo's code. We do so to facilitate downgrades. */
            u32 GetConfig() const { return m_config; }
            Result WriteContentMetaToPlaceHolder(InstallContentInfo *out_install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, util::optional<bool> is_temporary);

            StorageId GetInstallStorage() const { return m_install_storage; }

            virtual Result OnPrepareComplete() { R_SUCCEED(); }

            Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out);

            Result CanContinue();
        private:
            bool IsCancelRequested();
            Result PrepareImpl();
            Result ExecuteImpl();
            Result CommitImpl(const StorageContentMetaKey *keys, s32 num_keys);
            Result CleanupOne(const InstallContentMeta &content_meta);

            Result VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys);

            virtual Result PrepareInstallContentMetaData() = 0;
            virtual Result GetLatestVersion(util::optional<u32> *out_version, u64 id) { AMS_UNUSED(out_version, id); R_THROW(ncm::ResultContentMetaNotFound()); }

            virtual Result OnExecuteComplete() { R_SUCCEED(); }

            Result WritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info);
            virtual Result OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) = 0;

            bool IsNecessaryInstallTicket(const fs::RightsId &rights_id);
            virtual Result InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) = 0;

            Result IsNewerThanInstalled(bool *out, const ContentMetaKey &key);
            Result PreparePlaceHolder();

            void SetProgressState(InstallProgressState state);
            void IncrementProgress(s64 size);
            void SetTotalSize(s64 size);
            void SetLastResult(Result last_result);
            void CleanupProgress();

            void ResetThroughputMeasurement();
            void StartThroughputMeasurement();
            void UpdateThroughputMeasurement(s64 throughput);

            Result GetInstallContentMetaDataFromPath(AutoBuffer *out, const Path &path, const InstallContentInfo &content_info, util::optional<u32> source_version);

            InstallContentInfo MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, util::optional<bool> is_temporary);

            Result ReadContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const ContentMetaKey &key, fs::ContentAttributes attr);
            Result ListRightsIdsByInstallContentMeta(s32 *out_count, Span<RightsId> out_span, const InstallContentMeta &content_meta, s32 offset);
        public:
            virtual Result CheckInstallable() { R_SUCCEED(); }

            void SetFirmwareVariationId(FirmwareVariationId id) { m_firmware_variation_id = id; }
            Result ListRightsIds(s32 *out_count, Span<RightsId> out_span, const ContentMetaKey &key, s32 offset);
    };

}
