/*
 *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that 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 <https://www.gnu.org/licenses/>.
 */
#pragma once

#include "ip_utils.h"

#include <functional>
#include <vector>
#include <chrono>
#include <system_error>
#include <cstdint>

#if defined(_MSC_VER)
#include <BaseTsd.h>
using ssize_t = SSIZE_T;
#endif

namespace dhtnet {

template<typename T>
class GenericSocket
{
public:
    using ValueType = T;

    virtual ~GenericSocket() { shutdown(); }

    /**
     * Receive callback type.
     * @param buf Buffer containing received data
     * @param len Length of the received data
     * @return Number of bytes processed, zero or negative value to close the socket.
     * A negative value indicates an error, zero indicates a graceful shutdown.
     * In both cases the channel is shutdown immediately and synchronously,
     * guaranteeing that no further channel callback is called,
     * except the shutdown callback if set afterwards.
     */
    using RecvCb = std::function<ssize_t(const ValueType* buf, std::size_t len)>;

    /**
     * Close established connection.
     * @note Terminate outstanding blocking read operations with an empty error code, but a 0 read size.
     */
    virtual void shutdown() {}

    /**
     * Set Rx callback.
     * @warning This method is here for backward compatibility
     * and because async IO are not implemented yet.
     */
    virtual void setOnRecv(RecvCb&& cb) = 0;

    virtual bool isReliable() const = 0;

    virtual bool isInitiator() const = 0;

    /**
     * Return maximum application payload size.
     * This value is negative if the session is not ready to give a valid answer.
     * The value is 0 if such information is irrelevant for the session.
     * If stricly positive, the user must use send() with an input buffer size below or equals
     * to this value if it want to be sure that the transport sent it in an atomic way.
     * Example: in case of unreliable transport using packet oriented IO,
     * this value gives the maximal size used to send one packet.
     */
    virtual int maxPayload() const = 0;

    /**
     * Wait until data to read available, timeout or io error.
     * @param ec error code set in case of error (if return value is < 0)
     * @return positive number if data ready for read, 0 in case of timeout or error.
     * @note error code is not set in case of timeout, but set only in case of io error
     * (i.e. socket deconnection).
     * @note a graceful end of stream is reported as `asio::error::eof`, which lets a
     * caller tell it apart both from an idle timeout (no error code) and from an
     * abnormal disconnection (any other error code).
     * @todo make a std::chrono version for the timeout
     */
    virtual int waitForData(std::chrono::milliseconds timeout, std::error_code& ec) const = 0;

    /**
     * Write a given amount of data.
     * @param buf data to write.
     * @param len number of bytes to write.
     * @param ec error code set in case of error.
     * @return number of bytes written, 0 is valid.
     * @warning error checking consists in checking if \a !ec is true, not if returned size is 0
     * as a write of 0 could be considered a valid operation.
     */
    virtual std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) = 0;

    /**
     * Read a given amount of data.
     * @param buf data to read.
     * @param len number of bytes to read.
     * @param ec error code set in case of error.
     * @return number of bytes read, 0 is valid.
     * @warning error checking consists in checking if \a !ec is true, not if returned size is 0
     * as a read of 0 could be considered a valid operation (i.e. non-blocking IO).
     */
    virtual std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) = 0;

    /**
     * write() adaptor for STL containers.
     */
    template<typename U>
    std::size_t write(const U& obj, std::error_code& ec)
    {
        return write(obj.data(), obj.size() * sizeof(typename U::value_type), ec);
    }

    /**
     * read() adaptor for STL containers.
     */
    template<typename U>
    std::size_t read(U& storage, std::error_code& ec)
    {
        auto res = read(storage.data(), storage.size() * sizeof(typename U::value_type), ec);
        if (!ec)
            storage.resize(res);
        return res;
    }

    /**
     * Return the local IP address if known.
     * @note The address is not valid (addr.isUnspecified() returns true) if it's not known
     * or not available.
     */
    virtual IpAddr localAddr() const { return {}; }

    /**
     * Return the remote IP address if known.
     * @note The address is not valid (addr.isUnspecified() returns true) if it's not known
     * or not available.
     */
    virtual IpAddr remoteAddr() const { return {}; }

protected:
    GenericSocket() = default;
};

} // namespace dhtnet
