/*
 * UDPWorkload.actor.cpp
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2013-2020 Apple Inc. and the FoundationDB project authors
 *
 * 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.
 */

#include "fdbclient/FDBTypes.h"
#include "fdbclient/ReadYourWrites.h"
#include "fdbserver/workloads/workloads.actor.h"
#include "flow/ActorCollection.h"
#include "flow/Arena.h"
#include "flow/Error.h"
#include "flow/IRandom.h"
#include "flow/flow.h"
#include "flow/network.h"
#include "flow/actorcompiler.h" // has to be last include
#include "flow/serialize.h"
#include <functional>
#include <limits>
#include <unordered_map>
#include <vector>
#include <memory>
#include <functional>

namespace {

struct UDPWorkload : TestWorkload {
	constexpr static const char* name = "UDPWorkload";
	// config
	Key keyPrefix;
	double runFor;
	int minPort, maxPort;
	//  members
	NetworkAddress serverAddress;
	Reference<IUDPSocket> serverSocket;
	std::unordered_map<NetworkAddress, unsigned> sent, received, acked, successes;
	PromiseStream<NetworkAddress> toAck;

	UDPWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
		keyPrefix = getOption(options, "keyPrefix"_sr, "/udp/"_sr);
		runFor = getOption(options, "runFor"_sr, 60.0);
		minPort = getOption(options, "minPort"_sr, 5000);
		maxPort = getOption(options, "minPort"_sr, 6000);
		for (auto p : { minPort, maxPort }) {
			ASSERT(p > 0 && p < std::numeric_limits<unsigned short>::max());
		}
	}

	std::string description() const override { return name; }
	ACTOR static Future<Void> _setup(UDPWorkload* self, Database cx) {
		state NetworkAddress localAddress(g_network->getLocalAddress().ip,
		                                  deterministicRandom()->randomInt(self->minPort, self->maxPort + 1),
		                                  true,
		                                  false);
		state Key key = self->keyPrefix.withSuffix(BinaryWriter::toValue(self->clientId, Unversioned()));
		state Value serializedLocalAddress = BinaryWriter::toValue(localAddress, IncludeVersion());
		state ReadYourWritesTransaction tr(cx);
		Reference<IUDPSocket> s = wait(INetworkConnections::net()->createUDPSocket(localAddress.isV6()));
		self->serverSocket = std::move(s);
		self->serverSocket->bind(localAddress);
		self->serverAddress = localAddress;
		loop {
			try {
				Optional<Value> v = wait(tr.get(key));
				if (v.present()) {
					return Void();
				}
				tr.set(key, serializedLocalAddress);
				wait(tr.commit());
				return Void();
			} catch (Error& e) {
				wait(tr.onError(e));
			}
		}
	}
	Future<Void> setup(Database const& cx) override { return _setup(this, cx); }

	class Message {
		int _type = 0;

	public:
		enum class Type : uint8_t { PING, PONG };
		Message() {}
		explicit Message(Type t) {
			switch (t) {
			case Type::PING:
				_type = 0;
				break;
			case Type::PONG:
				_type = 1;
				break;
			default:
				UNSTOPPABLE_ASSERT(false);
			}
		}

		Type type() const {
			switch (_type) {
			case 0:
				return Type::PING;
			case 1:
				return Type::PONG;
			default:
				UNSTOPPABLE_ASSERT(false);
			}
		}

		template <class Ar>
		void serialize(Ar& ar) {
			serializer(ar, _type);
		}
	};

	static Message ping() { return Message{ Message::Type::PING }; }
	static Message pong() { return Message{ Message::Type::PONG }; }

	ACTOR static Future<Void> _receiver(UDPWorkload* self) {
		state Standalone<StringRef> packetString = makeString(IUDPSocket::MAX_PACKET_SIZE);
		state uint8_t* packet = mutateString(packetString);
		state NetworkAddress peerAddress;
		loop {
			int sz = wait(self->serverSocket->receiveFrom(packet, packet + IUDPSocket::MAX_PACKET_SIZE, &peerAddress));
			auto msg = BinaryReader::fromStringRef<Message>(packetString.substr(0, sz), IncludeVersion());
			if (msg.type() == Message::Type::PONG) {
				self->successes[peerAddress] += 1;
			} else if (msg.type() == Message::Type::PING) {
				self->received[peerAddress] += 1;
				self->toAck.send(peerAddress);
			} else {
				UNSTOPPABLE_ASSERT(false);
			}
		}
	}

	ACTOR static Future<Void> serverSender(UDPWorkload* self, std::vector<NetworkAddress>* remotes) {
		state Standalone<StringRef> packetString;
		state NetworkAddress peer;
		loop {
			choose {
				when(wait(delay(0.1))) {
					peer = deterministicRandom()->randomChoice(*remotes);
					packetString = BinaryWriter::toValue(Message{ Message::Type::PING }, IncludeVersion());
					self->sent[peer] += 1;
				}
				when(NetworkAddress p = waitNext(self->toAck.getFuture())) {
					peer = p;
					packetString = BinaryWriter::toValue(pong(), IncludeVersion());
					self->acked[peer] += 1;
				}
			}
			int res = wait(self->serverSocket->sendTo(packetString.begin(), packetString.end(), peer));
			ASSERT(res == packetString.size());
		}
	}

	ACTOR static Future<Void> clientReceiver(UDPWorkload* self, Reference<IUDPSocket> socket, Future<Void> done) {
		state Standalone<StringRef> packetString = makeString(IUDPSocket::MAX_PACKET_SIZE);
		state uint8_t* packet = mutateString(packetString);
		state NetworkAddress peer;
		state Future<Void> finished = Never();
		loop {
			choose {
				when(int sz = wait(socket->receiveFrom(packet, packet + IUDPSocket::MAX_PACKET_SIZE, &peer))) {
					auto res = BinaryReader::fromStringRef<Message>(packetString.substr(0, sz), IncludeVersion());
					ASSERT(res.type() == Message::Type::PONG);
					self->successes[peer] += 1;
				}
				when(wait(done)) {
					finished = delay(1.0);
					done = Never();
				}
				when(wait(finished)) { return Void(); }
			}
		}
	}

	ACTOR static Future<Void> clientSender(UDPWorkload* self, std::vector<NetworkAddress>* remotes) {
		state AsyncVar<Reference<IUDPSocket>> socket;
		state Standalone<StringRef> sendString;
		state ActorCollection actors(false);
		state NetworkAddress peer;

		loop {
			choose {
				when(wait(delay(0.1))) {}
				when(wait(actors.getResult())) { UNSTOPPABLE_ASSERT(false); }
			}
			if (!socket.get().isValid() || deterministicRandom()->random01() < 0.05) {
				peer = deterministicRandom()->randomChoice(*remotes);
				Reference<IUDPSocket> s = wait(INetworkConnections::net()->createUDPSocket(peer));
				socket.set(s);
				socket = s;
				actors.add(clientReceiver(self, socket.get(), socket.onChange()));
			}
			sendString = BinaryWriter::toValue(ping(), IncludeVersion());
			int res = wait(socket.get()->send(sendString.begin(), sendString.end()));
			ASSERT(res == sendString.size());
			self->sent[peer] += 1;
		}
	}

	ACTOR static Future<Void> _start(UDPWorkload* self, Database cx) {
		state ReadYourWritesTransaction tr(cx);
		state std::vector<NetworkAddress> remotes;
		loop {
			try {
				RangeResult range = wait(tr.getRange(prefixRange(self->keyPrefix), CLIENT_KNOBS->TOO_MANY));
				ASSERT(!range.more);
				for (auto const& p : range) {
					auto cID = BinaryReader::fromStringRef<decltype(self->clientId)>(
					    p.key.removePrefix(self->keyPrefix), Unversioned());
					if (cID != self->clientId) {
						remotes.emplace_back(BinaryReader::fromStringRef<NetworkAddress>(p.value, IncludeVersion()));
					}
				}
				break;
			} catch (Error& e) {
				wait(tr.onError(e));
			}
		}
		wait(clientSender(self, &remotes) && serverSender(self, &remotes) && _receiver(self));
		UNSTOPPABLE_ASSERT(false);
		return Void();
	}
	Future<Void> start(Database const& cx) override { return delay(runFor) || _start(this, cx); }
	Future<bool> check(Database const& cx) override { return true; }
	void getMetrics(vector<PerfMetric>& m) override {
		unsigned totalReceived = 0, totalSent = 0, totalAcked = 0, totalSuccess = 0;
		for (const auto& p : sent) {
			totalSent += p.second;
		}
		for (const auto& p : received) {
			totalReceived += p.second;
		}
		for (const auto& p : acked) {
			totalAcked += p.second;
		}
		for (const auto& p : successes) {
			totalSuccess += p.second;
		}
		m.emplace_back("Sent", totalSent, false);
		m.emplace_back("Received", totalReceived, false);
		m.emplace_back("Acknknowledged", totalAcked, false);
		m.emplace_back("Successes", totalSuccess, false);
	}
};

} // namespace

WorkloadFactory<UDPWorkload> UDPWorkloadFactory(UDPWorkload::name);
