// Copyright 2023 Google LLC.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

use conformance_proto::{ConformanceRequest, ConformanceResponse};
use conformance_rust_overlay_hack_proto::ConformanceRequestRustOverlayHack;

#[cfg(cpp_kernel)]
use protobuf_cpp as kernel;

#[cfg(upb_kernel)]
use protobuf_upb as kernel;

use kernel::Optional::{Set, Unset};

use std::io::{self, ErrorKind, Read, Write};
use test_messages_proto2::TestAllTypesProto2;
use test_messages_proto2_editions_proto::TestAllTypesProto2 as EditionsTestAllTypesProto2;
use test_messages_proto3::TestAllTypesProto3;
use test_messages_proto3_editions_proto::TestAllTypesProto3 as EditionsTestAllTypesProto3;

/// Returns Some(i32) if a binary read can succeed from stdin.
/// Returns None if we have reached an EOF.
/// Panics for any other error reading.
fn read_little_endian_i32_from_stdin() -> Option<i32> {
    let mut buffer = [0_u8; 4];
    if let Err(e) = io::stdin().read_exact(&mut buffer) {
        match e.kind() {
            ErrorKind::UnexpectedEof => None,
            _ => panic!("failed to read i32 from stdin"),
        }
    } else {
        Some(i32::from_le_bytes(buffer))
    }
}

/// Returns Some of a conformance request read from stdin.
/// Returns None if we have hit an EOF that suggests the test suite is complete.
/// Panics in any other case (e.g. an EOF in a place that would imply a
/// programmer error in the conformance test suite).
fn read_request_from_stdin() -> Option<(ConformanceRequest, ConformanceRequestRustOverlayHack)> {
    let msg_len = read_little_endian_i32_from_stdin()?;
    let mut serialized = vec![0_u8; msg_len as usize];
    io::stdin().read_exact(&mut serialized).unwrap();
    let mut req = ConformanceRequest::new();
    req.deserialize(&serialized).unwrap();

    // TODO: b/318373255 - Since enum accessors aren't available yet, we parse an
    // overlay with int32 field instead of an enum so that we can check if the
    // requested output is binary or not. This will be deleted once enum
    // accessors are supported.
    let mut req_overlay_hack = ConformanceRequestRustOverlayHack::new();
    req_overlay_hack.deserialize(&serialized).unwrap();

    Some((req, req_overlay_hack))
}

fn write_response_to_stdout(resp: &ConformanceResponse) {
    let bytes = resp.serialize();
    let len = bytes.len() as u32;
    let mut handle = io::stdout();
    handle.write_all(&len.to_le_bytes()).unwrap();
    handle.write(&bytes).unwrap();
    handle.flush().unwrap();
}

fn do_test(
    req: &ConformanceRequest,
    req_overlay_hack: &ConformanceRequestRustOverlayHack,
) -> ConformanceResponse {
    let mut resp = ConformanceResponse::new();
    let message_type = req.message_type();

    // TODO: b/318373255 - Use the enum once its supported.
    // if req.requested_output_format() != WireFormat.PROTOBUF {
    if req_overlay_hack.requested_output_format() != 1 {
        resp.skipped_mut().set("only wire format output implemented");
        return resp;
    }

    let bytes = match req.protobuf_payload_opt() {
        Unset(_) => {
            resp.skipped_mut().set("only wire format input implemented");
            return resp;
        }
        Set(bytes) => bytes,
    };

    let serialized = match message_type.as_bytes() {
        b"protobuf_test_messages.proto2.TestAllTypesProto2" => {
            let mut proto = TestAllTypesProto2::new();
            if let Err(_) = proto.deserialize(bytes) {
                resp.parse_error_mut().set("failed to parse bytes");
                return resp;
            }
            proto.serialize()
        }
        b"protobuf_test_messages.proto3.TestAllTypesProto3" => {
            let mut proto = TestAllTypesProto3::new();
            if let Err(_) = proto.deserialize(bytes) {
                resp.parse_error_mut().set("failed to parse bytes");
                return resp;
            }
            proto.serialize()
        }
        b"protobuf_test_messages.editions.proto2.TestAllTypesProto2" => {
            let mut proto = EditionsTestAllTypesProto2::new();
            if let Err(_) = proto.deserialize(bytes) {
                resp.parse_error_mut().set("failed to parse bytes");
                return resp;
            }
            proto.serialize()
        }
        b"protobuf_test_messages.editions.proto3.TestAllTypesProto3" => {
            let mut proto = EditionsTestAllTypesProto3::new();
            if let Err(_) = proto.deserialize(bytes) {
                resp.parse_error_mut().set("failed to parse bytes");
                return resp;
            }
            proto.serialize()
        }
        _ => panic!("unexpected msg type {message_type}"),
    };

    resp.protobuf_payload_mut().set(serialized);
    return resp;
}

fn main() {
    let mut total_runs = 0;
    while let Some((req, req_overlay_hack)) = read_request_from_stdin() {
        let resp = do_test(&req, &req_overlay_hack);
        write_response_to_stdout(&resp);
        total_runs += 1;
    }
    eprintln!("conformance_rust: received EOF from test runner after {total_runs} tests");
}