/*
 * 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 <utils/Mutex.h>

#include <map>
#include <vector>

#include <hardware/context_hub.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_EXT_APPS_ON        0 // () -> (char success)
#define NANOHUB_EXT_APPS_OFF       1 // () -> (char success)
#define NANOHUB_EXT_APP_DELETE     2 // (u64 name) -> (char success)    //idempotent
#define NANOHUB_QUERY_MEMINFO      3 // () -> (mem_info)
#define NANOHUB_QUERY_APPS         4 // (u32 idxStart) -> (app_info[idxStart] OR EMPTY IF NO MORE)
#define NANOHUB_QUERY_RSA_KEYS     5 // (u32 byteOffset) -> (u8 data[1 or more bytes] OR EMPTY IF NO MORE)
#define NANOHUB_START_UPLOAD       6 // (char isOs, u32 totalLenToTx) -> (char success)
#define NANOHUB_CONT_UPLOAD        7 // (u32 offset, u8 data[]) -> (char success)
#define NANOHUB_FINISH_UPLOAD      8 // () -> (char success)
#define NANOHUB_REBOOT             9 // () -> (char success)

// Custom defined private messages
#define CONTEXT_HUB_LOAD_OS (CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE + 1)


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

#define NANOHUB_UPLOAD_CHUNK_SZ_MAX 64
#define NANOHUB_MEM_SZ_UNKNOWN      0xFFFFFFFFUL

namespace android {

namespace nanohub {

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

struct NanohubAppInfo {
    hub_app_name_t name;
    uint32_t version, flashUse, ramUse;
} __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 cmd;
    int32_t status;
    NanohubRsp(MessageBuf &buf, bool no_status = false);
};

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:

    /*
     * 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) = 0;
        virtual int handleRx(MessageBuf &buf) = 0;
        virtual int getState() const = 0; // FSM state
        virtual int getStatus() const = 0; // execution status (result code)
        virtual ~ISession() {}
    };

    class SessionManager;

    class Session : public ISession {
        friend class SessionManager;

        mutable Mutex mDoneLock; // controls condition and state transitions
        Condition mDoneWait;
        volatile int mState;

    protected:
        mutable Mutex mLock; // serializes message handling
        int32_t mStatus;

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

        void complete() {
            Mutex::Autolock _l(mDoneLock);
            if (mState != SESSION_DONE) {
                mState = SESSION_DONE;
                mDoneWait.broadcast();
            }
        }
        void setState(int state) {
            if (state == SESSION_DONE) {
                complete();
            } else {
                Mutex::Autolock _l(mDoneLock);
                mState = state;
            }
        }
    public:
        Session() { mState = SESSION_INIT; mStatus = -1; }
        int getStatus() const {
            Mutex::Autolock _l(mLock);
            return mStatus;
        }
        int wait() {
            Mutex::Autolock _l(mDoneLock);
            while (mState != SESSION_DONE) {
                mDoneWait.wait(mDoneLock);
            }
            return 0;
        }
        virtual int getState() const override {
            Mutex::Autolock _l(mDoneLock);
            return mState;
        }
        virtual bool isDone() const {
            Mutex::Autolock _l(mDoneLock);
            return mState == SESSION_DONE;
        }
        virtual bool isRunning() const {
            Mutex::Autolock _l(mDoneLock);
            return mState > SESSION_DONE;
        }
    };

    class AppMgmtSession : public Session {
        enum {
            TRANSFER = SESSION_USER,
            FINISH,
            RELOAD,
            REBOOT,
            MGMT,
        };
        uint32_t mCmd; // UPLOAD_APP | UPPLOAD_OS
        uint32_t mResult;
        std::vector<uint8_t> mData;
        uint32_t mLen;
        uint32_t mPos;

        int setupMgmt(const hub_message_t *appMsg, uint32_t cmd);
        int handleTransfer(NanohubRsp &rsp);
        int handleFinish(NanohubRsp &rsp);
        int handleReload(NanohubRsp &rsp);
        int handleReboot(NanohubRsp &rsp);
        int handleMgmt(NanohubRsp &rsp);
    public:
        AppMgmtSession() {
            mCmd = 0;
            mResult = 0;
            mPos = 0;
            mLen = 0;
        }
        virtual int handleRx(MessageBuf &buf) override;
        virtual int setup(const hub_message_t *app_msg) override;
    };

    class MemInfoSession : public Session {
    public:
        virtual int setup(const hub_message_t *app_msg) override;
        virtual int handleRx(MessageBuf &buf) override;
    };

    class KeyInfoSession  : public Session {
        std::vector<uint8_t> mRsaKeyData;
        int requestRsaKeys(void);
    public:
        virtual int setup(const hub_message_t *) override;
        virtual int handleRx(MessageBuf &buf) override;
        bool haveKeys() const {
            Mutex::Autolock _l(mLock);
            return mRsaKeyData.size() > 0 && !isRunning();
        }
    };

    class AppInfoSession : public Session {
        std::vector<hub_app_info> mAppInfo;
        int requestNext();
    public:
        virtual int setup(const hub_message_t *) override;
        virtual int handleRx(MessageBuf &buf) override;
    };

    class GlobalSession : public Session {
    public:
        virtual int setup(const hub_message_t *) override;
        virtual int handleRx(MessageBuf &buf) override;
    };

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

        Mutex lock;
        SessionMap sessions_;
        GlobalSession mGlobal;

        void next(SessionMap::iterator &pos)
        {
            Mutex::Autolock _l(lock);
            pos->second->isDone() ? pos = sessions_.erase(pos) : ++pos;
        }

    public:
        SessionManager() {
            mGlobal.setup(nullptr);
        }
        int handleRx(MessageBuf &buf);
        int setup_and_add(int id, Session *session, const hub_message_t *appMsg) {
            Mutex::Autolock _l(lock);
            if (sessions_.count(id) == 0 && !session->isRunning()) {
                int ret = session->setup(appMsg);
                if (ret < 0) {
                    session->complete();
                } else {
                    sessions_[id] = session;
                }
                return ret;
            }
            return -EBUSY;
        }

    } mSessions;

    const hub_app_name_t mHostIfAppName = {
        .id = NANO_APP_ID(NANOAPP_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);
    int doHandleRx(const nano_message *rxMsg);

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

    KeyInfoSession mKeySession;
    AppMgmtSession mAppMgmtSession;
    AppInfoSession mAppInfoSession;
    MemInfoSession mMemInfoSession;

public:
    static int handleTx(const hub_message_t *txMsg) {
        return getSystem()->doHandleTx(txMsg);
    }
    static int handleRx(const nano_message *rxMsg) {
        return getSystem()->doHandleRx(rxMsg);
    }
};

}; // namespace nanohub

}; // namespace android

#endif