// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

#include "runtime/pull_load_task_mgr.h"

#include <stdio.h>

#include <functional>
#include <vector>
#include <sstream>

#include "common/logging.h"
#include "gen_cpp/BackendService_types.h"
#include "util/defer_op.h"
#include "util/file_utils.h"
#include "util/thrift_util.h"
#include "util/debug_util.h"

namespace doris {

class PullLoadTaskCtx {
public:
    PullLoadTaskCtx();
    PullLoadTaskCtx(const TUniqueId& id, int num_senders);

    Status from_thrift(const uint8_t* buf, uint32_t len);

    const TUniqueId& id() const {
        return _task_info.id;
    }

    Status add_sub_task_info(const TPullLoadSubTaskInfo& sub_task_info, bool* finish);

    void get_task_info(TPullLoadTaskInfo* task_info) const {
        std::lock_guard<std::mutex> l(_lock);
        *task_info = _task_info;
    }

    void get_task_info(std::vector<TPullLoadTaskInfo>* task_infos) const {
        std::lock_guard<std::mutex> l(_lock);
        task_infos->push_back(_task_info);
    }

    Status serialize(ThriftSerializer* serializer) {
        std::lock_guard<std::mutex> l(_lock);
        return serializer->serialize(&_task_info);
    }

private:
    mutable std::mutex _lock;
    int _num_senders;

    std::set<int> _finished_senders;
    TPullLoadTaskInfo _task_info;
};

PullLoadTaskCtx::PullLoadTaskCtx() : _num_senders(0) {
}

PullLoadTaskCtx::PullLoadTaskCtx(const TUniqueId& id, int num_senders) 
        : _num_senders(num_senders) {
    _task_info.id = id;
    _task_info.etl_state = TEtlState::RUNNING;
}

Status PullLoadTaskCtx::from_thrift(const uint8_t* buf, uint32_t len) {
    return deserialize_thrift_msg(buf, &len, true, &_task_info);
}

Status PullLoadTaskCtx::add_sub_task_info(
        const TPullLoadSubTaskInfo& sub_task_info, bool* finish) {
    std::lock_guard<std::mutex> l(_lock);
    if (_finished_senders.count(sub_task_info.sub_task_id) > 0) {
        // Already receive this sub-task informations
        return Status::OK;
    }

    // Apply this information
    for (auto& it : sub_task_info.file_map) {
        _task_info.file_map.emplace(it.first, it.second);
    }

    if (sub_task_info.__isset.tracking_url) {
        _task_info.tracking_urls.push_back(sub_task_info.tracking_url);
    }

    _finished_senders.insert(sub_task_info.sub_task_id);
    if (_finished_senders.size() == _num_senders) {
        // We have already receive all sub-task informations.
        _task_info.etl_state = TEtlState::FINISHED;
        *finish = true;
    }

    return Status::OK;
}

PullLoadTaskMgr::PullLoadTaskMgr(const std::string& path) 
        : _path(path), _dir_exist(true) {
}

PullLoadTaskMgr::~PullLoadTaskMgr() {
}

Status PullLoadTaskMgr::init() {
    auto st = load_task_ctxes();
    if (!st.ok()) {
        LOG(WARNING) << "Load task from directory failed. because " << st.get_error_msg();
        _dir_exist = false;
    }
    return Status::OK;
}

Status PullLoadTaskMgr::load_task_ctxes() {
    // 1. scan all files
    std::vector<std::string> files;
    RETURN_IF_ERROR(FileUtils::scan_dir(_path, &files));

    // 2. load
    for (auto& file : files) {
        if (!is_valid_task_file(file)) {
            continue;
        }
        std::string file_path = _path + "/" + file;
        Status status = load_task_ctx(file_path);
        if (!status.ok()) {
            LOG(WARNING) << "Load one file failed. file_name:" << file_path 
                << ", status:" << status.get_error_msg();
        }
    }

    return Status::OK;
}

Status PullLoadTaskMgr::load_task_ctx(const std::string& file_path) {
    FILE* fp = fopen(file_path.c_str(), "r");
    if (fp == nullptr) {
        char buf[64];
        std::stringstream ss;
        ss << "fopen(" << file_path << ") failed, because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }
    DeferOp close_file(std::bind(&fclose, fp));

    // 1. read content length
    uint32_t content_len = 0;
    size_t res = fread(&content_len, 4, 1, fp);
    if (res != 1) {
        char buf[64];
        std::stringstream ss;
        ss << "fread content length failed, because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }

    if (content_len > 10 * 1024 * 1024) {
        return Status("Content is too big.");
    }

    // 2. read content
    uint8_t* content = new uint8_t[content_len];
    DeferOp close_content(std::bind<void>(std::default_delete<uint8_t[]>(), content));
    res = fread(content, content_len, 1, fp);
    if (res != 1) {
        char buf[64];
        std::stringstream ss;
        ss << "fread content failed, because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }

    // 3. checksum
    uint32_t checksum = 0;
    checksum = HashUtil::crc_hash(content, content_len, checksum);

    uint32_t read_checksum = 0;
    res = fread(&read_checksum, 4, 1, fp);
    if (res != 1) {
        char buf[64];
        std::stringstream ss;
        ss << "fread content failed, because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }
    if (read_checksum != checksum) {
        std::stringstream ss;
        ss << "fread checksum failed, read_checksum=" << read_checksum 
            << ", content_checksum=" << checksum;
        return Status(ss.str());
    }

    // 4. new task context
    std::shared_ptr<PullLoadTaskCtx> task_ctx(new PullLoadTaskCtx());
    RETURN_IF_ERROR(task_ctx->from_thrift(content, content_len));

    {
        std::lock_guard<std::mutex> l(_lock);
        _task_ctx_map.emplace(task_ctx->id(), task_ctx);
    }

    LOG(INFO) << "success load task " << task_ctx->id();
    return Status::OK;
}

Status PullLoadTaskMgr::save_task_ctx(PullLoadTaskCtx* task_ctx) {
    if (!_dir_exist) {
        return Status::OK;
    }
    ThriftSerializer serializer(true, 64 * 1024);
    RETURN_IF_ERROR(task_ctx->serialize(&serializer));
    uint8_t* content = nullptr;
    uint32_t content_len = 0;
    serializer.get_buffer(&content, &content_len);

    std::string file_path = task_file_path(task_ctx->id());

    FILE* fp = fopen(file_path.c_str(), "w");
    if (fp == nullptr) {
        char buf[64];
        std::stringstream ss;
        ss << "fopen(" << file_path << ") failed, because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }
    DeferOp close_file(std::bind(&fclose, fp));

    // 1. write content size
    size_t res = fwrite(&content_len, 4, 1, fp);
    if (res != 1) {
        char buf[64];
        std::stringstream ss;
        ss << "fwrite content length failed., because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }

    // 2. write content
    res = fwrite(content, content_len, 1, fp);
    if (res != 1) {
        char buf[64];
        std::stringstream ss;
        ss << "fwrite content failed., because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }

    // 3. checksum
    uint32_t checksum = 0;
    checksum = HashUtil::crc_hash(content, content_len, checksum);
    res = fwrite(&checksum, 4, 1, fp);
    if (res != 1) {
        char buf[64];
        std::stringstream ss;
        ss << "fwrite checksum failed., because: " << strerror_r(errno, buf, 64);
        return Status(ss.str());
    }

    return Status::OK;
}

Status PullLoadTaskMgr::register_task(const TUniqueId& id, int num_senders) {
    {
        std::lock_guard<std::mutex> l(_lock);
        auto it = _task_ctx_map.find(id);
        if (it != std::end(_task_ctx_map)) {
            // Do nothing
            LOG(INFO) << "Duplicate pull load task, id=" << id << " num_senders=" << num_senders;
            return Status::OK;
        }

        std::shared_ptr<PullLoadTaskCtx> task_ctx(new PullLoadTaskCtx(id, num_senders));
        _task_ctx_map.emplace(id, task_ctx);
    }
    LOG(INFO) << "Register pull load task, id=" << id << ", num_senders=" << num_senders;
    return Status::OK;
}

std::string PullLoadTaskMgr::task_file_path(const TUniqueId& id) const {
    std::stringstream ss; 
    ss << _path << "/task_" << id.hi << "_" << id.lo;
    return ss.str();
}

bool PullLoadTaskMgr::is_valid_task_file(const std::string& file_name) const {
    if (file_name.find("task_") == 0) {
        return true;
    }
    return false;
}

Status PullLoadTaskMgr::deregister_task(const TUniqueId& id) {
    std::shared_ptr<PullLoadTaskCtx> ctx;
    {
        std::lock_guard<std::mutex> l(_lock);
        auto it = _task_ctx_map.find(id);
        if (it == std::end(_task_ctx_map)) {
            LOG(INFO) << "Deregister unknown pull load task, id=" << id;
            return Status::OK;
        }
        _task_ctx_map.erase(it);
        ctx = it->second;
    }
    
    if (ctx != nullptr && _dir_exist) {
        std::string file_path = task_file_path(id);
        remove(file_path.c_str());
    }
    LOG(INFO) << "Deregister pull load task, id=" << id;
    
    return Status::OK;
}

Status PullLoadTaskMgr::report_sub_task_info(
        const TPullLoadSubTaskInfo& sub_task_info) {
    std::shared_ptr<PullLoadTaskCtx> ctx;
    {
        std::lock_guard<std::mutex> l(_lock);
        auto it = _task_ctx_map.find(sub_task_info.id);
        if (it == std::end(_task_ctx_map)) {
            std::stringstream ss;
            ss << "receive unknown pull load sub-task id=" << sub_task_info.id 
                << ", sub_id=" << sub_task_info.sub_task_id;
            return Status(ss.str());
        }

        ctx = it->second;
    }
    bool is_finished = false;
    RETURN_IF_ERROR(ctx->add_sub_task_info(sub_task_info, &is_finished));
    if (is_finished) {
        auto st = save_task_ctx(ctx.get());
        if (!st.ok()) {
            LOG(INFO) << "Save pull load task context failed.id=" << sub_task_info.id;
        }
    }
    VLOG_RPC << "process one pull load sub-task, id=" << sub_task_info.id 
        << ", sub_id=" << sub_task_info.sub_task_id;
    return Status::OK;
}

Status PullLoadTaskMgr::fetch_task_info(const TUniqueId& tid, 
                                        TFetchPullLoadTaskInfoResult* result) {
    std::shared_ptr<PullLoadTaskCtx> ctx;
    {
        std::lock_guard<std::mutex> l(_lock);
        auto it = _task_ctx_map.find(tid);
        if (it == std::end(_task_ctx_map)) {
            LOG(INFO) << "Fetch unknown task info, id=" << tid;
            result->task_info.id = tid;
            result->task_info.etl_state = TEtlState::CANCELLED;
            return Status::OK;
        }

        ctx = it->second;
    }
    ctx->get_task_info(&result->task_info);
    return Status::OK;
}

Status PullLoadTaskMgr::fetch_all_task_infos(
        TFetchAllPullLoadTaskInfosResult* result) {
    std::lock_guard<std::mutex> l(_lock);
    for (auto& it : _task_ctx_map) {
        it.second->get_task_info(&result->task_infos);
    }
    return Status::OK;
}

}
