//
// DO NOT EDIT THIS FILE!
// THIS FILE IS AUTOMATICALLY GENERATED FROM A LITERATE PROGRAM SOURCE FILE.
//
//
// This software is copyrighted 2023 by G. Andrew Mangogna.
// The following terms apply to all files associated with the software unless
// explicitly disclaimed in individual files.
// 
// The author hereby grants permission to use, copy, modify, distribute,
// and license this software and its documentation for any purpose, provided
// that existing copyright notices are retained in all copies and that this
// notice is included verbatim in any distributions. No written agreement,
// license, or royalty fee is required for any of the authorized uses.
// Modifications to this software may be copyrighted by their authors and
// need not follow the licensing terms described here, provided that the
// new terms are clearly indicated on the first page of each file where
// they apply.
// 
// IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
// OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES
// THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
// 
// THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
// IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
// NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
// OR MODIFICATIONS.
// 
// GOVERNMENT USE: If you are acquiring this software on behalf of the
// U.S. government, the Government shall have only "Restricted Rights"
// in the software and related documentation as defined in the Federal
// Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
// are acquiring the software on behalf of the Department of Defense,
// the software shall be classified as "Commercial Computer Software"
// and the Government shall have only "Restricted Rights" as defined in
// Clause 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing,
// the authors grant the U.S. Government and others acting in its behalf
// permission to use and distribute the software in accordance with the
// terms specified in this license.
//

const std = @import("std");
const hai = @import("hashed_array_index.zig");
const HashedArrayIndex = hai.HashedArrayIndex;
const SequenceCount = hai.SequenceCount;

const rand = std.rand;
const process = std.process;
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;
const StaticBitSet = std.StaticBitSet;

const assert = std.debug.assert;
const Service = struct {
    host: []const u8,
    service: u16,
    protocol: Protocol,
    inserted: usize = 0,
    removed: usize = 0,
};
const service_id = [_][]const u8{
    "host", "service", "protocol",
};
const Protocol = enum {
    tcp,
    udp,
    ddp,
};
var services = [_]Service{
    .{.host = "localhost", .service = 7, .protocol = .tcp},
    .{.host = "localhost", .service = 7, .protocol = .udp},
    .{.host = "localhost", .service = 13, .protocol = .tcp},
    .{.host = "localhost", .service = 13, .protocol = .udp},
    .{.host = "localhost", .service = 22, .protocol = .tcp},
    .{.host = "localhost", .service = 161, .protocol = .tcp},
    .{.host = "localhost", .service = 161, .protocol = .udp},
    .{.host = "localhost", .service = 69, .protocol = .udp},
    .{.host = "localhost", .service = 80, .protocol = .tcp},
    .{.host = "localhost", .service = 443, .protocol = .tcp},
    .{.host = "localhost", .service = 554, .protocol = .tcp},
    .{.host = "localhost", .service = 554, .protocol = .udp},
    .{.host = "localhost", .service = 5432, .protocol = .tcp},
};
const ServiceIndex = HashedArrayIndex(Service, services.len, &service_id, 100);
var services_index: ServiceIndex = .{};
const IndexTracker = StaticBitSet(services.len);
var service_indexed = IndexTracker.initEmpty();
pub fn main() !void {
    const out = io.getStdOut();
    var buf = io.bufferedWriter(out.writer());
    var w = buf.writer();
    defer buf.flush() catch {};
    errdefer buf.flush() catch {};

    const stderr = io.getStdErr().writer();

    var prng_seed: u64 = 0;
    var max_iterations: usize = 10_000_000;

    var arg_iter = process.args();
    var arg0 = arg_iter.next().?;
    var arg0_iter = mem.splitBackwardsScalar(u8, arg0, '/');
    const cmd_name = arg0_iter.next().?;


    while (arg_iter.next()) |arg| {
        if (mem.eql(u8, arg, "--seed")) {
            const seed_arg = arg_iter.next() orelse {
                try stderr.print("{s}: missing random number generator seed argument\n",
                        .{cmd_name});
                try stderr.print("expected: --seed <prng_seed>\n", .{});
                return error.MissingArg;
            };
            prng_seed = fmt.parseUnsigned(@TypeOf(prng_seed), seed_arg, 0)
                    catch |err| {
                        try stderr.print("{s}: bad random number seed argument, \"{s}\"\n",
                                .{cmd_name, seed_arg});
                        return err;
                    };
        } else if (mem.eql(u8, arg, "--count")) {
            const count_arg = arg_iter.next() orelse {
                try stderr.print("{s}: missing iteration count argument\n", .{cmd_name});
                try stderr.print("expected: --count <max_count>\n", .{});
                return error.MissingArg;
            };
            max_iterations = fmt.parseUnsigned(@TypeOf(max_iterations), count_arg, 0)
                    catch |err| {
                        try stderr.print("{s}: bad count argument, \"{s}\"\n",
                                .{cmd_name, count_arg});
                        return err;
                    };
        } else if (mem.eql(u8, arg, "--help")) {
            try stderr.print("usage: {s} ?--seed <prng_seed>? ?--count <max_count>?\n",
                    .{cmd_name});
            return;
        } else {
            try stderr.print("{s}: unknown argument \"{s}\"\n", .{cmd_name, arg});
            try stderr.print("usage: {s} ?--seed prng_seed? ?--count max_count?\n",
                    .{cmd_name});
            return error.UnknownArg;
        }
    }

    var gen = rand.DefaultPrng.init(prng_seed);
    var max_occupied: SequenceCount = 0;

    // The basic idea is to use the bit set to determine if a given service element
    // is in the index of the service array. If the element is already in the
    // index, it is removed.  If the element is not indexed, then it is added
    // to the index. Any time an element is inserted into the index, we verify
    // that we can find it. Conversely, any element removed from the index
    // is verified that it is no longer there.
    // A random number generator is used to determine which
    // service array element to examine. Repeat millions of times.
    // Print some statistics when done.

    var counter: usize = 0;
    while (counter < max_iterations) : (counter += 1) {
        var index = rand.limitRangeBiased(u64, gen.next(), services.len);
        if (service_indexed.isSet(index)) {
            const found = services_index.search(&services[index]);
            assert(found != null);
            const removed = services_index.remove(&services[index]);
            assert(removed != null);
            removed.?.removed += 1;
            service_indexed.unset(index);
        } else {
            var inserted = services_index.insert(&services[index]);
            assert(inserted);
            services[index].inserted += 1;
            const found = services_index.search(&services[index]);
            assert(found != null);
            service_indexed.set(index);
        }

        max_occupied = @max(max_occupied, services_index.occupied);
    }

    try w.print("maximum number occupied buckets: {d}\n", .{max_occupied});

    try w.print("elements remaining in the hash table: {d}\n",
            .{service_indexed.count()});

    var index_iter = service_indexed.iterator(.{});
    while (index_iter.next()) |s_index| {
        const removed = services_index.remove(&services[s_index]);
        assert(removed != null);
        removed.?.removed += 1;
    }

    var total_insertions: usize = 0;
    var total_removals: usize = 0;
    for (services, 0..) |service, ndx| {
        try w.print("{d}: inserted = {d}, removed = {d}\n",
            .{ndx, service.inserted, service.removed});
        total_insertions += service.inserted;
        total_removals += service.removed;
    }

    try w.print ("total insertions = {d}\n", .{total_insertions});
    try w.print ("total removals = {d}\n", .{total_removals});
}
