
const std = @import("std");

pub const Reader = struct {
    const Self = @This();
    pub const ReadError = error{EOF};

    buf: []u8,
    remain: []u8,
    allocator: *std.mem.Allocator,

    pub fn init(buf: []u8, allocator: *std.mem.Allocator) Reader {
        return Reader{
            .buf = buf,
            .remain = buf,
            .allocator = allocator,
        };
    }

    pub fn read(this: *Self, count: usize) ![]u8 {
        const read_count = std.math.min(count, this.remain.len);
        if (read_count < count) {
            return error.EOF;
        }

        var slice = this.remain[0..read_count];

        this.remain = this.remain[read_count..];

        return slice;
    }

    pub fn readAs(this: *Self, comptime T: type) !T {
        if (!std.meta.trait.hasUniqueRepresentation(T)) {
            @compileError(@typeName(T) ++ " must have unique representation.");
        }

        return std.mem.bytesAsValue(T, try this.read(@sizeOf(T)));
    }

    pub fn readByte(this: *Self) !u8 {
        return (try this.read(1))[0];
    }

    pub fn readEnum(this: *Self, comptime Enum: type) !Enum {
        const E = error{
            /// An integer was read, but it did not match any of the tags in the supplied enum.
            InvalidValue,
        };
        const type_info = @typeInfo(Enum).Enum;
        const tag = try this.readInt(type_info.tag_type);

        inline for (std.meta.fields(Enum)) |field| {
            if (tag == field.value) {
                return @field(Enum, field.name);
            }
        }

        return E.InvalidValue;
    }

    pub fn readArray(this: *Self, comptime T: type) ![]const T {
        const length = try this.readInt(u32);
        if (length == 0) {
            return &([_]T{});
        }

        switch (T) {
            u8 => {
                return try this.read(length);
            },
            u16, u32, i8, i16, i32 => {
                return std.mem.readIntSliceNative(T, this.read(length * @sizeOf(T)));
            },
            []const u8 => {
                var i: u32 = 0;
                var array = try this.allocator.alloc([]const u8, length);
                while (i < length) : (i += 1) {
                    array[i] = try this.readArray(u8);
                }
                return array;
            },
            else => {
                switch (@typeInfo(T)) {
                    .Struct => |Struct| {
                        switch (Struct.layout) {
                            .Packed => {
                                const sizeof = @sizeOf(T);
                                var slice = try this.read(sizeof * length);
                                return std.mem.bytesAsSlice(T, slice);
                            },
                            else => {},
                        }
                    },
                    .Enum => |type_info| {
                        const enum_values = try this.read(length * @sizeOf(type_info.tag_type));
                        return @ptrCast([*]T, enum_values.ptr)[0..length];
                    },
                    else => {},
                }

                var i: u32 = 0;
                var array = try this.allocator.alloc(T, length);
                while (i < length) : (i += 1) {
                    array[i] = try this.readValue(T);
                }

                return array;
            },
        }
    }

    pub fn readByteArray(this: *Self) ![]u8 {
        const length = try this.readInt(u32);
        if (length == 0) {
            return &([_]u8{});
        }

        return try this.read(@intCast(usize, length));
    }

    pub fn readInt(this: *Self, comptime T: type) !T {
        var slice = try this.read(@sizeOf(T));

        return std.mem.readIntSliceNative(T, slice);
    }

    pub fn readBool(this: *Self) !bool {
        return (try this.readByte()) > 0;
    }

    pub fn readValue(this: *Self, comptime T: type) !T {
        switch (T) {
            bool => {
                return try this.readBool();
            },
            u8 => {
                return try this.readByte();
            },
            []const u8 => {
                return try this.readArray(u8);
            },

            []const []const u8 => {
                return try this.readArray([]const u8);
            },
            []u8 => {
                return try this.readArray([]u8);
            },
            u16, u32, i8, i16, i32 => {
                return std.mem.readIntSliceNative(T, try this.read(@sizeOf(T)));
            },
            else => {
                switch (@typeInfo(T)) {
                    .Struct => |Struct| {
                        switch (Struct.layout) {
                            .Packed => {
                                const sizeof = @sizeOf(T);
                                var slice = try this.read(sizeof);
                                return @ptrCast(*T, slice[0..sizeof]).*;
                            },
                            else => {},
                        }
                    },
                    .Enum => |type_info| {
                        return try this.readEnum(T);
                    },
                    else => {},
                }

                return try T.decode(this);
            },
        }

        @compileError("Invalid type passed to readValue");
    }
};

pub fn Writer(comptime WritableStream: type) type {
    return struct {
        const Self = @This();
        writable: WritableStream,

        pub fn init(writable: WritableStream) Self {
            return Self{ .writable = writable };
        }

        pub fn write(this: *Self, bytes: anytype) !void {
            _ = try this.writable.write(bytes);
        }

        pub fn writeByte(this: *Self, byte: u8) !void {
            _ = try this.writable.write(&[1]u8{byte});
        }

        pub fn writeInt(this: *Self, int: anytype) !void {
            try this.write(std.mem.asBytes(&int));
        }

        pub fn writeFieldID(this: *Self, comptime id: comptime_int) !void {
            try this.writeByte(id);
        }

        pub fn writeEnum(this: *Self, val: anytype) !void {
            try this.writeInt(@enumToInt(val));
        }

        pub fn writeValue(this: *Self, slice: anytype) !void {
            switch (@TypeOf(slice)) {
                []u16,
                []u32,
                []i16,
                []i32,
                []i8,
                []const u16,
                []const u32,
                []const i16,
                []const i32,
                []const i8,
                => {
                    try this.writeArray(@TypeOf(slice), slice);
                },

                []u8, []const u8 => {
                    try this.writeArray(u8, slice);
                },

                u8 => {
                    try this.write(slice);
                },
                u16, u32, i16, i32, i8 => {
                    try this.write(std.mem.asBytes(slice));
                },

                else => {
                    try slice.encode(this);
                },
            }
        }

        pub fn writeArray(this: *Self, comptime T: type, slice: anytype) !void {
            try this.writeInt(@truncate(u32, slice.len));

            switch (T) {
                u8 => {
                    try this.write(slice);
                },
                u16, u32, i16, i32, i8 => {
                    try this.write(std.mem.asBytes(slice));
                },
                []u8,
                []u16,
                []u32,
                []i16,
                []i32,
                []i8,
                []const u8,
                []const u16,
                []const u32,
                []const i16,
                []const i32,
                []const i8,
                => {
                    for (slice) |num_slice| {
                        try this.writeArray(std.meta.Child(@TypeOf(num_slice)), num_slice);
                    }
                },
                else => {
                    for (slice) |val| {
                        try val.encode(this);
                    }
                },
            }
        }

        pub fn endMessage(this: *Self) !void {
            try this.writeByte(0);
        }
    };
}

pub const ByteWriter = Writer(*std.io.FixedBufferStream([]u8));
pub const FileWriter = Writer(std.fs.File);




pub const analytics = struct { 

pub const OperatingSystem = enum(u8) {

_none,
  /// linux
  linux,

  /// macos
  macos,

  /// windows
  windows,

  /// wsl
  wsl,

_,

                pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void {
                    return try std.json.stringify(@tagName(self), opts, o);
                }

                
};

pub const Architecture = enum(u8) {

_none,
  /// x64
  x64,

  /// arm
  arm,

_,

                pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void {
                    return try std.json.stringify(@tagName(self), opts, o);
                }

                
};

pub const Platform = struct {
/// os
os: OperatingSystem,

/// arch
arch: Architecture,

/// version
version: []const u8,


pub fn decode(reader: anytype) anyerror!Platform {
  var this = std.mem.zeroes(Platform);

  this.os = try reader.readValue(OperatingSystem); 
  this.arch = try reader.readValue(Architecture); 
  this.version = try reader.readArray(u8); 
   return this;
}

pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
   try writer.writeEnum(this.os);
   try writer.writeEnum(this.arch);
   try writer.writeArray(u8, this.version);
}

};

pub const EventKind = enum(u32) {

_none,
  /// bundle_success
  bundle_success,

  /// bundle_fail
  bundle_fail,

  /// http_start
  http_start,

  /// http_build
  http_build,

  /// bundle_start
  bundle_start,

_,

                pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void {
                    return try std.json.stringify(@tagName(self), opts, o);
                }

                
};

pub const Uint64 = packed struct {
/// first
first: u32 = 0,

/// second
second: u32 = 0,


pub fn decode(reader: anytype) anyerror!Uint64 {
  var this = std.mem.zeroes(Uint64);

  this.first = try reader.readValue(u32); 
  this.second = try reader.readValue(u32); 
   return this;
}

pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
   try writer.writeInt(this.first);
   try writer.writeInt(this.second);
}

};

pub const EventListHeader = struct {
/// machine_id
machine_id: Uint64,

/// session_id
session_id: u32 = 0,

/// platform
platform: Platform,

/// build_id
build_id: u32 = 0,

/// project_id
project_id: Uint64,

/// session_length
session_length: u32 = 0,

/// feature_usage
feature_usage: u32 = 0,


pub fn decode(reader: anytype) anyerror!EventListHeader {
  var this = std.mem.zeroes(EventListHeader);

  this.machine_id = try reader.readValue(Uint64); 
  this.session_id = try reader.readValue(u32); 
  this.platform = try reader.readValue(Platform); 
  this.build_id = try reader.readValue(u32); 
  this.project_id = try reader.readValue(Uint64); 
  this.session_length = try reader.readValue(u32); 
  this.feature_usage = try reader.readValue(u32); 
   return this;
}

pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
   try writer.writeValue(this.machine_id);
   try writer.writeInt(this.session_id);
   try writer.writeValue(this.platform);
   try writer.writeInt(this.build_id);
   try writer.writeValue(this.project_id);
   try writer.writeInt(this.session_length);
   try writer.writeInt(this.feature_usage);
}

};

pub const EventHeader = struct {
/// timestamp
timestamp: Uint64,

/// kind
kind: EventKind,


pub fn decode(reader: anytype) anyerror!EventHeader {
  var this = std.mem.zeroes(EventHeader);

  this.timestamp = try reader.readValue(Uint64); 
  this.kind = try reader.readValue(EventKind); 
   return this;
}

pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
   try writer.writeValue(this.timestamp);
   try writer.writeEnum(this.kind);
}

};

pub const EventList = struct {
/// header
header: EventListHeader,

/// event_count
event_count: u32 = 0,


pub fn decode(reader: anytype) anyerror!EventList {
  var this = std.mem.zeroes(EventList);

  this.header = try reader.readValue(EventListHeader); 
  this.event_count = try reader.readValue(u32); 
   return this;
}

pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
   try writer.writeValue(this.header);
   try writer.writeInt(this.event_count);
}

};


};


const ExamplePackedStruct = packed struct {
    len: u32 = 0,
    offset: u32 = 0,

    pub fn encode(this: *const ExamplePackedStruct, writer: anytype) !void {
        try writer.write(std.mem.asBytes(this));
    }

    pub fn decode(reader: anytype) !ExamplePackedStruct {
        return try reader.readAs(ExamplePackedStruct);
    }
};

const ExampleStruct = struct {
    name: []const u8 = "",
    age: u32 = 0,

    pub fn encode(this: *const ExampleStruct, writer: anytype) !void {
        try writer.writeArray(u8, this.name);
        try writer.writeInt(this.age);
    }

    pub fn decode(reader: anytype) !ExampleStruct {
        var this = std.mem.zeroes(ExampleStruct);
        this.name = try reader.readArray(u8);
        this.age = try reader.readInt(u32);

        return this;
    }
};

const EnumValue = enum(u8) { hey, hi, heyopoo };

const ExampleMessage = struct {
    examples: ?[]ExampleStruct = &([_]ExampleStruct{}),
    pack: ?[]ExamplePackedStruct = &([_]ExamplePackedStruct{}),
    hey: ?u8 = 0,
    hey16: ?u16 = 0,
    hey32: ?u16 = 0,
    heyi32: ?i32 = 0,
    heyi16: ?i16 = 0,
    heyi8: ?i8 = 0,
    boolean: ?bool = null,
    heyooo: ?EnumValue = null,

    pub fn encode(this: *const ExampleMessage, writer: anytype) !void {
        if (this.examples) |examples| {
            try writer.writeFieldID(1);
            try writer.writeArray(ExampleStruct, examples);
        }

        if (this.pack) |pack| {
            try writer.writeFieldID(2);
            try writer.writeArray(ExamplePackedStruct, pack);
        }

        if (this.hey) |hey| {
            try writer.writeFieldID(3);
            try writer.writeInt(hey);
        }
        if (this.hey16) |hey16| {
            try writer.writeFieldID(4);
            try writer.writeInt(hey16);
        }
        if (this.hey32) |hey32| {
            try writer.writeFieldID(5);
            try writer.writeInt(hey32);
        }
        if (this.heyi32) |heyi32| {
            try writer.writeFieldID(6);
            try writer.writeInt(heyi32);
        }
        if (this.heyi16) |heyi16| {
            try writer.writeFieldID(7);
            try writer.writeInt(heyi16);
        }
        if (this.heyi8) |heyi8| {
            try writer.writeFieldID(8);
            try writer.writeInt(heyi8);
        }
        if (this.boolean) |boolean| {
            try writer.writeFieldID(9);
            try writer.writeInt(boolean);
        }

        if (this.heyooo) |heyoo| {
            try writer.writeFieldID(10);
            try writer.writeEnum(heyoo);
        }

        try writer.endMessage();
    }

    pub fn decode(reader: anytype) !ExampleMessage {
        var this = std.mem.zeroes(ExampleMessage);
        while (true) {
            switch (try reader.readByte()) {
                0 => {
                    return this;
                },

                1 => {
                    this.examples = try reader.readArray(std.meta.Child(@TypeOf(this.examples.?)));
                },
                2 => {
                    this.pack = try reader.readArray(std.meta.Child(@TypeOf(this.pack.?)));
                },
                3 => {
                    this.hey = try reader.readValue(@TypeOf(this.hey.?));
                },
                4 => {
                    this.hey16 = try reader.readValue(@TypeOf(this.hey16.?));
                },
                5 => {
                    this.hey32 = try reader.readValue(@TypeOf(this.hey32.?));
                },
                6 => {
                    this.heyi32 = try reader.readValue(@TypeOf(this.heyi32.?));
                },
                7 => {
                    this.heyi16 = try reader.readValue(@TypeOf(this.heyi16.?));
                },
                8 => {
                    this.heyi8 = try reader.readValue(@TypeOf(this.heyi8.?));
                },
                9 => {
                    this.boolean = try reader.readValue(@TypeOf(this.boolean.?));
                },
                10 => {
                    this.heyooo = try reader.readValue(@TypeOf(this.heyooo.?));
                },
                else => {
                    return error.InvalidValue;
                },
            }
        }

        return this;
    }
};

test "ExampleMessage" {
    var base = std.mem.zeroes(ExampleMessage);
    base.hey = 1;
    var buf: [4096]u8 = undefined;
    var writable = std.io.fixedBufferStream(&buf);
    var writer = ByteWriter.init(writable);
    var examples = [_]ExamplePackedStruct{
        .{ .len = 2, .offset = 5 },
        .{ .len = 0, .offset = 10 },
    };

    var more_examples = [_]ExampleStruct{
        .{ .name = "bacon", .age = 10 },
        .{ .name = "slime", .age = 300 },
    };
    base.examples = &more_examples;
    base.pack = &examples;
    base.heyooo = EnumValue.hey;
    try base.encode(&writer);
    var reader = Reader.init(&buf, std.heap.c_allocator);
    var compare = try ExampleMessage.decode(&reader);
    try std.testing.expectEqual(base.hey orelse 255, 1);

    const cmp_pack = compare.pack.?;
    for (cmp_pack) |item, id| {
        try std.testing.expectEqual(item, examples[id]);
    }

    const cmp_ex = compare.examples.?;
    for (cmp_ex) |item, id| {
        try std.testing.expectEqualStrings(item.name, more_examples[id].name);
        try std.testing.expectEqual(item.age, more_examples[id].age);
    }

    try std.testing.expectEqual(cmp_pack[0].len, examples[0].len);
    try std.testing.expectEqual(base.heyooo, compare.heyooo);
}
