/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.
 */

#ifndef _NANOHUB_SYSTEM_COMMS_H_
#define _NANOHUB_SYSTEM_COMMS_H_

#include <utils/Condition.h>

#include <chrono>
#include <condition_variable>
#include <map>
#include <mutex>
#include <vector>

#include <hardware/context_hub.h>
#include <nanohub/nanohub.h>

#include "nanohubhal.h"
#include "message_buf.h"

//rx: return 0 if handled, > 0 if not handled, < 0 if error happened

#define MSG_HANDLED 0

//messages to the HostIf nanoapp & their replies (mesages and replies both begin with u8 message_type)
#define NANOHUB_HAL_APP_MGMT      0x10 // (char cmd, u64 appId, u64 appMsk) -> (int errno, u32 results)

#define NANOHUB_HAL_APP_MGMT_START      0
#define NANOHUB_HAL_APP_MGMT_STOP       1
#define NANOHUB_HAL_APP_MGMT_UNLOAD     2
#define NANOHUB_HAL_APP_MGMT_DELETE     3

#define NANOHUB_HAL_SYS_MGMT      0x11 // (char cmd) -> (int errno)

#define NANOHUB_HAL_SYS_MGMT_ERASE      0
#define NANOHUB_HAL_SYS_MGMT_REBOOT     1

#define NANOHUB_HAL_APP_INFO      0x12

#define NANOHUB_HAL_APP_INFO_APPID          0x00
#define NANOHUB_HAL_APP_INFO_CRC            0x01
#define NANOHUB_HAL_APP_INFO_TID            0x02
#define NANOHUB_HAL_APP_INFO_VERSION        0x03
#define NANOHUB_HAL_APP_INFO_ADDR           0x04
#define NANOHUB_HAL_APP_INFO_SIZE           0x05
#define NANOHUB_HAL_APP_INFO_HEAP           0x06
#define NANOHUB_HAL_APP_INFO_DATA           0x07
#define NANOHUB_HAL_APP_INFO_BSS            0x08
#define NANOHUB_HAL_APP_INFO_CHRE_MAJOR     0x09
#define NANOHUB_HAL_APP_INFO_CHRE_MINOR     0x0A
#define NANOHUB_HAL_APP_INFO_END            0xFF

#define NANOHUB_HAL_SYS_INFO      0x13

#define NANOHUB_HAL_SYS_INFO_HEAP_FREE      0x0F
#define NANOHUB_HAL_SYS_INFO_RAM_SIZE       0x12
#define NANOHUB_HAL_SYS_INFO_EEDATA_SIZE    0x13
#define NANOHUB_HAL_SYS_INFO_EEDATA_FREE    0x14
#define NANOHUB_HAL_SYS_INFO_CODE_SIZE      0x15
#define NANOHUB_HAL_SYS_INFO_CODE_FREE      0x16
#define NANOHUB_HAL_SYS_INFO_SHARED_SIZE    0x17
#define NANOHUB_HAL_SYS_INFO_SHARED_FREE    0x18
#define NANOHUB_HAL_SYS_INFO_END            0xFF

#define NANOHUB_HAL_KEY_INFO      0x14
#define NANOHUB_HAL_START_UPLOAD  0x16
#define NANOHUB_HAL_CONT_UPLOAD   0x17
#define NANOHUB_HAL_FINISH_UPLOAD 0x18

#define NANOHUB_HAL_UPLOAD_ACCEPTED         0
#define NANOHUB_HAL_UPLOAD_WAIT             1
#define NANOHUB_HAL_UPLOAD_RESEND           2
#define NANOHUB_HAL_UPLOAD_RESTART          3
#define NANOHUB_HAL_UPLOAD_CANCEL           4
#define NANOHUB_HAL_UPLOAD_CANCEL_NO_RETRY  5
#define NANOHUB_HAL_UPLOAD_NO_SPACE         6

#define NANOHUB_APP_NOT_LOADED  (-1)
#define NANOHUB_APP_LOADED      (0)

#define NANOHUB_UPLOAD_CHUNK_SZ_MAX 64
#define NANOHUB_MEM_SZ_UNKNOWN      0xFFFFFFFFUL
#define NANOHUB_TID_UNKNOWN         0xFFFFFFFFUL

#define CONTEXT_HUB_START_APPS      8

namespace android {

namespace nanohub {

int system_comms_handle_rx(const nano_message_raw *msg);
int system_comms_handle_tx(const hub_message_t *outMsg);

struct MgmtStatus {
    union {
        uint32_t value;
        struct {
            uint8_t app;
            uint8_t task;
            uint8_t op;
            uint8_t erase;
        } __attribute__((packed));
    };
} __attribute__((packed));

struct NanohubMemInfo {
    //sizes
    uint32_t flashSz, blSz, osSz, sharedSz, eeSz;
    uint32_t ramSz;

    //use
    uint32_t blUse, osUse, sharedUse, eeUse;
    uint32_t ramUse;
} __attribute__((packed));

struct NanohubRsp {
    uint32_t mCmd;
    uint32_t mTransactionId;
    int32_t mStatus;
    explicit NanohubRsp(MessageBuf &buf, uint32_t transactionId, bool chre);
};

inline bool operator == (const hub_app_name_t &a, const hub_app_name_t &b) {
    return a.id == b.id;
}

inline bool operator != (const hub_app_name_t &a, const hub_app_name_t &b) {
    return !(a == b);
}

class SystemComm {
private:

    class AppManager;

    /*
     * Nanohub HAL sessions
     *
     * Session is an object that can group several message exchanges with FW,
     * maintain state, and be waited for completion by someone else.
     *
     * As of this moment, since all sessions are triggered by client thread,
     * and all the exchange is happening in local worker thread, it is only possible
     * for client thread to wait on session completion.
     * Allowing sessions to wait on each other will require a worker thread pool.
     * It is now unnecessary, and not implemented.
     */
    class ISession {
    public:
        virtual int setup(const hub_message_t *app_msg, uint32_t transactionId, AppManager &appManager) = 0;
        virtual int handleRx(MessageBuf &buf, uint32_t transactionId, AppManager &appManager, bool chre) = 0;
        virtual int getState() const = 0; // FSM state
        virtual int getStatus() const = 0; // execution status (result code)
        virtual void abort(int32_t) = 0;
        virtual ~ISession() {}
    };

    class SessionManager;

    class Session : public ISession {
        friend class SessionManager;

        mutable std::mutex mDoneMutex; // controls condition and state transitions
        std::condition_variable mDoneCond;
        volatile int mState;

    protected:
        mutable std::mutex mLock; // serializes message handling
        int32_t mStatus;

        enum {
            SESSION_INIT = 0,
            SESSION_DONE = 1,
            SESSION_USER = 2,
        };

        void complete() {
            std::unique_lock<std::mutex> lk(mDoneMutex);
            if (mState != SESSION_DONE) {
                mState = SESSION_DONE;
                lk.unlock();
                mDoneCond.notify_all();
            }
        }
        void abort(int32_t status) {
            std::lock_guard<std::mutex> _l(mLock);
            mStatus = status;
            complete();
        }
        void setState(int state) {
            if (state == SESSION_DONE) {
                complete();
            } else {
                std::lock_guard<std::mutex> _l(mDoneMutex);
                mState = state;
            }
        }
    public:
        Session() { mState = SESSION_INIT; mStatus = -1; }
        int getStatus() const {
            std::lock_guard<std::mutex> _l(mLock);
            return mStatus;
        }
        int wait() {
            std::unique_lock<std::mutex> lk(mDoneMutex);
            bool success = mDoneCond.wait_for(
                    lk, std::chrono::seconds(30),
                    [this] { return mState == SESSION_DONE; });
            if (!success) {
                ALOGE("Timed out waiting for response");
            }
            return success ? 0 : -1;
        }
        virtual int getState() const override {
            std::lock_guard<std::mutex> _l(mDoneMutex);
            return mState;
        }
        virtual bool isDone() const {
            std::lock_guard<std::mutex> _l(mDoneMutex);
            return mState == SESSION_DONE;
        }
        virtual bool isRunning() const {
            std::lock_guard<std::mutex> _l(mDoneMutex);
            return mState > SESSION_DONE;
        }
    };

    class AppMgmtSession : public Session {
        enum {
            TRANSFER = SESSION_USER,
            QUERY_START,
            START,
            STOP_TRANSFER,
            FINISH,
            RUN,
            STOP_RUN,
            RUN_FAILED,
            REBOOT,
            ERASE_TRANSFER,
            MGMT,
            INFO,
        };
        uint32_t mCmd; // LOAD_APP, UNLOAD_APP, ENABLE_APP, DISABLE_APP
        uint32_t mResult;
        std::vector<uint8_t> mData;
        uint32_t mLen;
        uint32_t mPos;
        uint32_t mNextPos;
        uint32_t mErrCnt;
        hub_app_name_t mAppName;
        uint32_t mFlashAddr;
        std::vector<hub_app_name_t> mAppList;

        int setupMgmt(const hub_message_t *appMsg, uint32_t transactionId, uint32_t cmd, AppManager &appManager);
        int handleTransfer(NanohubRsp &rsp, MessageBuf &, AppManager &appManager);
        int handleStopTransfer(NanohubRsp &rsp, MessageBuf &buf, AppManager &);
        int handleQueryStart(NanohubRsp &rsp, MessageBuf &buf, AppManager &appManager);
        int handleStart(NanohubRsp &rsp, MessageBuf &buf, AppManager &);
        int handleFinish(NanohubRsp &rsp, MessageBuf &buf, AppManager &appManager);
        int handleRun(NanohubRsp &rsp, MessageBuf &buf, AppManager &appManager);
        int handleStopRun(NanohubRsp &rsp, MessageBuf &buf, AppManager &);
        int handleReboot(NanohubRsp &rsp, MessageBuf &buf, AppManager &);
        int handleEraseTransfer(NanohubRsp &rsp, MessageBuf &buf, AppManager &appManager);
        int handleMgmt(NanohubRsp &rsp, MessageBuf &buf, AppManager &appManager);
        int handleInfo(NanohubRsp &rsp, MessageBuf &buf, AppManager &appManager);
    public:
        AppMgmtSession() {
            mCmd = 0;
            mResult = 0;
            mPos = 0;
            mLen = 0;
            memset(&mAppName, 0, sizeof(mAppName));
        }
        virtual int handleRx(MessageBuf &buf, uint32_t transactionId, AppManager &appManager, bool chre) override;
        virtual int setup(const hub_message_t *app_msg, uint32_t transactionId, AppManager &appManager) override;
    };

    class MemInfoSession : public Session {
    public:
        virtual int setup(const hub_message_t *app_msg, uint32_t transactionId, AppManager &) override;
        virtual int handleRx(MessageBuf &buf, uint32_t transactionId, AppManager &, bool chre) override;
    };

    class KeyInfoSession  : public Session {
        std::vector<uint8_t> mRsaKeyData;
        uint32_t mKeyNum;
        uint32_t mKeyOffset;
        int requestRsaKeys(uint32_t transactionId);
    public:
        virtual int setup(const hub_message_t *, uint32_t, AppManager &) override;
        virtual int handleRx(MessageBuf &buf, uint32_t transactionId, AppManager &, bool chre) override;
        bool haveKeys() const {
            std::lock_guard<std::mutex> _l(mLock);
            return mRsaKeyData.size() > 0 && !isRunning();
        }
    };

    class AppManager {
        struct AppData {
            uint32_t version, flashUse, ramUse;
            uint32_t tid, crc, flashAddr;
            uint8_t chre_major, chre_minor;
            bool chre, running, loaded;

            bool cached_start, cached_napp;
            uint32_t cached_version, cached_crc;
        };

        typedef std::map<uint64_t, std::unique_ptr<AppData>> AppMap;

        AppMap apps_;

    public:
        AppManager() {
            restoreApps();
        }
        void dumpAppInfo(std::string &result);
        bool saveApps();
        bool restoreApps();
        bool eraseApps();
        bool writeApp(hub_app_name_t &appName, const uint8_t *data, int32_t len);
        int32_t readApp(hub_app_name_t &appName, void **data);
        bool cmpApp(hub_app_name_t &appName, const uint8_t *data, uint32_t len);
        uint32_t readNanohubAppInfo(MessageBuf &buf);
        void sendAppInfoToApp(uint32_t transactionId);
        int getAppsToStart(std::vector<hub_app_name_t> &apps);
        bool setCachedCrc(hub_app_name_t &appName, uint32_t crc) {
            if (!isAppPresent(appName))
                return false;
            else {
                apps_[appName.id]->cached_napp = true;
                apps_[appName.id]->cached_crc = crc;
                apps_[appName.id]->cached_start = false;
                saveApps();
                return true;
            }
        }
        bool clearCachedApp(hub_app_name_t &appName) {
            if (!isAppPresent(appName))
                return false;
            else {
                apps_[appName.id]->cached_napp = false;
                apps_[appName.id]->cached_start = false;
                saveApps();
                return true;
            }
        }
        bool clearRunning(hub_app_name_t &appName) {
            if (!isAppLoaded(appName))
                return false;
            else {
                apps_[appName.id]->running = false;
                return true;
            }
        }

        bool setCachedVersion(hub_app_name_t &appName, uint32_t version) {
            if (!isAppPresent(appName))
                return false;
            else {
                apps_[appName.id]->cached_version = version;
                return true;
            }
        }
        bool setCachedStart(hub_app_name_t &appName, bool start) {
            if (!isAppPresent(appName))
                return false;
            else {
                apps_[appName.id]->cached_start = start;
                saveApps();
                return true;
            }
        }
        bool addNewApp(hub_app_name_t &appName, uint32_t version) {
            if (isAppLoaded(appName))
                return false;
            else
                apps_[appName.id] = std::unique_ptr<AppData>(new AppData);
            apps_[appName.id]->loaded = false;
            apps_[appName.id]->running = false;
            apps_[appName.id]->chre = false;
            apps_[appName.id]->cached_napp = false;
            apps_[appName.id]->cached_version = version;
            return true;
        }
        bool isAppPresent(hub_app_name_t &appName) {
            return apps_.count(appName.id) != 0;
        }
        bool isAppLoaded(hub_app_name_t &appName) {
            return apps_.count(appName.id) != 0 && apps_[appName.id]->loaded;
        }
        bool isAppRunning(hub_app_name_t &appName) {
            return apps_.count(appName.id) != 0 && apps_[appName.id]->running;
        }
        uint32_t getFlashAddr(hub_app_name_t &appName) {
            if (isAppPresent(appName))
                return apps_[appName.id]->flashAddr;
            else
                return 0xFFFFFFFF;
        }
    };

    class SessionManager {
        typedef std::map<int, Session* > SessionMap;

        std::mutex lock;
        SessionMap sessions_;

        bool isActive(const SessionMap::iterator &pos) const
        {
            return !pos->second->isDone();
        }
        void next(SessionMap::iterator &pos)
        {
            isActive(pos) ? pos++ : pos = sessions_.erase(pos);
        }

    public:
        int handleRx(MessageBuf &buf, uint32_t transactionId, AppManager &appManager, bool chre, bool &reboot, uint32_t &rebootStatus);
        int setup_and_add(int id, Session *session, const hub_message_t *appMsg, uint32_t transactionId, AppManager &appManager);
    } mSessions;

    const hub_app_name_t mHostIfAppName = {
        .id = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0)
    };

    static SystemComm *getSystem() {
        // this is thread-safe in c++11
        static SystemComm theInstance;
        return &theInstance;
    }

    SystemComm () = default;
    ~SystemComm() = default;

    int doHandleTx(const hub_message_t *txMsg, uint32_t transactionId);
    int doHandleRx(uint64_t appId, uint32_t transactionId, const char *data, int len, bool chre);
    void doDumpAppInfo(std::string &result);

    int doHandleRx(const nano_message_raw *rxMsg) {
        return doHandleRx(rxMsg->hdr.appId, 0, reinterpret_cast<const char*>(rxMsg->data), rxMsg->hdr.len, false);
    }

    int doHandleRx(const nano_message_chre *rxMsg) {
        return doHandleRx(rxMsg->hdr.appId, rxMsg->hdr.appEventId, reinterpret_cast<const char*>(rxMsg->data), rxMsg->hdr.len, true);
    }

    static void sendToApp(uint32_t typ, uint32_t transactionId, const void *data, uint32_t len) {
        if (NanoHub::messageTracingEnabled()) {
            dumpBuffer("HAL -> APP", get_hub_info()->os_app_name, transactionId, 0, data, len);
        }
        NanoHub::sendToApp(HubMessage(&get_hub_info()->os_app_name, typ, transactionId, ENDPOINT_BROADCAST, data, len));
    }
    static int sendToSystem(const void *data, size_t len, uint32_t transactionId);

    KeyInfoSession mKeySession;
    AppMgmtSession mAppMgmtSession;
    MemInfoSession mMemInfoSession;
    AppManager     mAppManager;

public:
    static int handleTx(const hub_message_t *txMsg, uint32_t transactionId) {
        return getSystem()->doHandleTx(txMsg, transactionId);
    }
    static int handleRx(const nano_message_raw *rxMsg) {
        return getSystem()->doHandleRx(rxMsg);
    }
    static int handleRx(const nano_message_chre *rxMsg) {
        return getSystem()->doHandleRx(rxMsg);
    }
    static void dumpAppInfo(std::string &result) {
        return getSystem()->doDumpAppInfo(result);
    }
};

}; // namespace nanohub

}; // namespace android

#endif