/*
 * Copyright (C) 2018 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.
 */

#pragma once

#include <string.h>

#include <algorithm>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include <android-base/logging.h>

#include "fdevent/fdevent.h"
#include "sysdeps/uio.h"

// Essentially std::vector<char>, except without zero initialization or reallocation.
// Features a position attribute to allow sequential read/writes for copying between Blocks.
struct Block {
    using iterator = char*;

    Block() = default;

    explicit Block(size_t size) { allocate(size); }

    explicit Block(const std::string& s) : Block(s.begin(), s.end()) {}

    template <typename Iterator>
    Block(Iterator begin, Iterator end) : Block(end - begin) {
        std::copy(begin, end, data_.get());
    }

    Block(const Block& copy) = delete;
    Block(Block&& move) noexcept
        : data_(std::exchange(move.data_, nullptr)),
          capacity_(std::exchange(move.capacity_, 0)),
          size_(std::exchange(move.size_, 0)),
          position_(std::exchange(move.position_, 0)) {}

    Block& operator=(const Block& copy) = delete;
    Block& operator=(Block&& move) noexcept {
        clear();
        data_ = std::exchange(move.data_, nullptr);
        capacity_ = std::exchange(move.capacity_, 0);
        size_ = std::exchange(move.size_, 0);
        position_ = std::exchange(move.size_, 0);
        return *this;
    }

    ~Block() = default;

    void resize(size_t new_size) {
        if (!data_) {
            allocate(new_size);
        } else {
            CHECK_GE(capacity_, new_size);
            size_ = new_size;
        }
    }

    template <typename InputIt>
    void assign(InputIt begin, InputIt end) {
        clear();
        allocate(end - begin);
        std::copy(begin, end, data_.get());
    }

    void clear() {
        data_.reset();
        capacity_ = 0;
        size_ = 0;
        position_ = 0;
    }

    bool is_full() const { return remaining() == 0; }

    size_t remaining() const { return size_ - position_; }

    size_t fillFrom(Block& from) {
        size_t size = std::min(remaining(), from.remaining());
        memcpy(&data_[position_], &from.data_[from.position_], size);
        position_ += size;
        from.position_ += size;
        return size;
    }

    void rewind() { position_ = 0; }
    size_t position() const { return position_; }

    size_t capacity() const { return capacity_; }
    size_t size() const { return size_; }
    bool empty() const { return size() == 0; }

    char* data() { return data_.get(); }
    const char* data() const { return data_.get(); }

    char* begin() { return data_.get(); }
    const char* begin() const { return data_.get(); }

    char* end() { return data() + size_; }
    const char* end() const { return data() + size_; }

    char& operator[](size_t idx) { return data()[idx]; }
    const char& operator[](size_t idx) const { return data()[idx]; }

    bool operator==(const Block& rhs) const {
        static_assert(std::is_standard_layout<decltype(data())>());
        return size() == rhs.size() && memcmp(data(), rhs.data(), size()) == 0;
    }

  private:
    void allocate(size_t size) {
        CHECK(data_ == nullptr);
        CHECK_EQ(0ULL, capacity_);
        CHECK_EQ(0ULL, size_);
        if (size != 0) {
            // This isn't std::make_unique because that's equivalent to `new char[size]()`, which
            // value-initializes the array instead of leaving it uninitialized. As an optimization,
            // call new without parentheses to avoid this costly initialization.
            data_.reset(new char[size]);
            capacity_ = size;
            size_ = size;
        }
    }

    std::unique_ptr<char[]> data_;
    size_t capacity_ = 0;
    size_t size_ = 0;
    size_t position_ = 0;
};

struct amessage {
    uint32_t command;     /* command identifier constant      */
    uint32_t arg0;        /* first argument                   */
    uint32_t arg1;        /* second argument                  */
    uint32_t data_length; /* length of payload (0 is allowed) */
    uint32_t data_check;  /* checksum of data payload         */
    uint32_t magic;       /* command ^ 0xffffffff             */
};

struct apacket {
    using payload_type = Block;
    amessage msg;
    payload_type payload;
};

struct IOVector {
    using value_type = char;
    using block_type = Block;
    using size_type = size_t;

    IOVector() = default;

    explicit IOVector(block_type&& block) { append(std::move(block)); }

    IOVector(const IOVector& copy) = delete;
    IOVector(IOVector&& move) noexcept : IOVector() { *this = std::move(move); }

    IOVector& operator=(const IOVector& copy) = delete;
    IOVector& operator=(IOVector&& move) noexcept;

    const value_type* front_data() const {
        if (chain_.empty()) {
            return nullptr;
        }

        return chain_[start_index_].data() + begin_offset_;
    }

    size_type front_size() const {
        if (chain_.empty()) {
            return 0;
        }

        return chain_[start_index_].size() - begin_offset_;
    }

    size_type size() const { return chain_length_ - begin_offset_; }
    bool empty() const { return size() == 0; }

    // Return the last block so the caller can still reuse its allocated capacity
    // or it can be simply ignored.
    block_type clear();

    void drop_front(size_type len);

    // Split the first |len| bytes out of this chain into its own.
    IOVector take_front(size_type len);

    // Add a nonempty block to the chain.
    void append(block_type&& block) {
        if (block.size() == 0) {
            return;
        }
        CHECK_NE(0ULL, block.size());
        chain_length_ += block.size();
        chain_.emplace_back(std::move(block));
    }

    void trim_front();

  private:
    void trim_chain_front();

    // Drop the front block from the chain, and update chain_length_ appropriately.
    void pop_front_block();

    // Iterate over the blocks with a callback with an operator()(const char*, size_t).
    template <typename Fn>
    void iterate_blocks(Fn&& callback) const {
        if (size() == 0) {
            return;
        }

        for (size_t i = start_index_; i < chain_.size(); ++i) {
            const auto& block = chain_[i];
            const char* begin = block.data();
            size_t length = block.size();

            if (i == start_index_) {
                CHECK_GE(block.size(), begin_offset_);
                begin += begin_offset_;
                length -= begin_offset_;
            }
            callback(begin, length);
        }
    }

  public:
    // Copy all of the blocks into a single block.
    template <typename CollectionType = block_type>
    CollectionType coalesce() const& {
        CollectionType result;
        if (size() == 0) {
            return result;
        }

        result.resize(size());

        size_t offset = 0;
        iterate_blocks([&offset, &result](const char* data, size_t len) {
            static_assert(std::is_standard_layout<decltype(result)>());
            memcpy(&result[offset], data, len);
            offset += len;
        });

        return result;
    }

    block_type coalesce() &&;

    template <typename FunctionType>
    auto coalesced(FunctionType&& f) const {
        if (chain_.size() == start_index_ + 1) {
            // If we only have one block, we can use it directly.
            return f(chain_[start_index_].data() + begin_offset_, size());
        } else {
            // Otherwise, copy to a single block.
            auto data = coalesce();
            return f(data.data(), data.size());
        }
    }

    // Get a list of iovecs that can be used to write out all of the blocks.
    std::vector<adb_iovec> iovecs() const;

  private:
    // Total length of all of the blocks in the chain.
    size_t chain_length_ = 0;

    size_t begin_offset_ = 0;
    size_t start_index_ = 0;
    std::vector<block_type> chain_;
};

// An implementation of weak pointers tied to the fdevent run loop.
//
// This allows for code to submit a request for an object, and upon receiving
// a response, know whether the object is still alive, or has been destroyed
// because of other reasons. We keep a list of living weak_ptrs in each object,
// and clear the weak_ptrs when the object is destroyed. This is safe, because
// we require that both the destructor of the referent and the get method on
// the weak_ptr are executed on the main thread.
template <typename T>
struct enable_weak_from_this;

template <typename T>
struct weak_ptr {
    weak_ptr() = default;
    explicit weak_ptr(T* ptr) { reset(ptr); }
    weak_ptr(const weak_ptr& copy) { reset(copy.get()); }

    weak_ptr(weak_ptr&& move) {
        reset(move.get());
        move.reset();
    }

    ~weak_ptr() { reset(); }

    weak_ptr& operator=(const weak_ptr& copy) {
        if (&copy == this) {
            return *this;
        }

        reset(copy.get());
        return *this;
    }

    weak_ptr& operator=(weak_ptr&& move) {
        if (&move == this) {
            return *this;
        }

        reset(move.get());
        move.reset();
        return *this;
    }

    T* get() const {
        fdevent_check_looper();
        return ptr_;
    }

    void reset(T* ptr = nullptr) {
        fdevent_check_looper();

        if (ptr == ptr_) {
            return;
        }

        if (ptr_) {
            ptr_->weak_ptrs_.erase(
                    std::remove(ptr_->weak_ptrs_.begin(), ptr_->weak_ptrs_.end(), this));
        }

        ptr_ = ptr;
        if (ptr_) {
            ptr_->weak_ptrs_.push_back(this);
        }
    }

  private:
    friend struct enable_weak_from_this<T>;
    T* ptr_ = nullptr;
};

template <typename T>
struct enable_weak_from_this {
    ~enable_weak_from_this() {
        if (!weak_ptrs_.empty()) {
            fdevent_check_looper();
            for (auto& weak : weak_ptrs_) {
                weak->ptr_ = nullptr;
            }
            weak_ptrs_.clear();
        }
    }

    weak_ptr<T> weak() { return weak_ptr<T>(static_cast<T*>(this)); }

    void schedule_deletion() {
        fdevent_run_on_looper([this]() { delete static_cast<T*>(this); });
    }

  private:
    friend struct weak_ptr<T>;
    std::vector<weak_ptr<T>*> weak_ptrs_;
};
