/* Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

use bytes::Bytes;
use futures_util::stream::StreamExt;
use iggy::prelude::*;
use iggy_common::wire_conversions::user_headers_to_wire;
// The compression and decompression utilities are shared between the producer and consumer compression examples.
// Hence, we import them here.
use iggy_examples::shared::codec::{Codec, NUM_MESSAGES, STREAM_NAME, TOPIC_NAME};

pub const CONSUMER_NAME: &str = "example-consumer";

#[tokio::main]
async fn main() -> Result<(), IggyError> {
    // Setup a client to connect to the iggy-server via TCP.
    let client = IggyClientBuilder::new().with_tcp().build()?;
    client.connect().await?;

    // Login using default credentials.
    client
        .login_user(DEFAULT_ROOT_USERNAME, DEFAULT_ROOT_PASSWORD)
        .await?;

    // Configure a consumer, to read the compressed messages that were previously written to the server (using ../producer/main.rs).
    let mut consumer = client
        .consumer(CONSUMER_NAME, STREAM_NAME, TOPIC_NAME, 0)?
        .build();
    consumer.init().await?;

    // Prevent the consumer from indefinitely awaiting new messages.
    // Since the producer wrote 1000 messages to the server, break if all of them were read (consumed_messages == NUM_MESSAGES).
    let mut consumed_messages = 0;
    while let Some(message) = consumer.next().await {
        let mut received_message = message.expect("Message was not received from server.");
        handle_payload_compression(&mut received_message)?;
        consumed_messages += 1;
        println!(
            "Message payload was decompressed and reads: {:?}",
            received_message.message.payload
        );
        if consumed_messages == NUM_MESSAGES {
            return Ok(());
        }
    }

    Ok(())
}

// A helper function to decompress a ReceivedMessage's payload.
fn handle_payload_compression(msg: &mut ReceivedMessage) -> Result<(), IggyError> {
    // Check if the message payload is compressed by inspecting the user-header.
    if let Ok(Some(algorithm)) = msg.message.get_user_header(&Codec::header_key()) {
        // setup the codec with the compression algorithm defined in the user-header (value)
        let codec = Codec::from_header_value(&algorithm);

        // decompress the payload and update the payload length
        let decompressed_payload = codec.decompress(&msg.message.payload);
        msg.message.payload = Bytes::from(decompressed_payload);
        msg.message.header.payload_length = msg.message.payload.len() as u32;

        // remove the compression header since payload is now decompressed
        if let Ok(Some(mut headers_map)) = msg.message.user_headers_map() {
            headers_map.remove(&Codec::header_key());
            if headers_map.is_empty() {
                msg.message.header.user_headers_length = 0;
                msg.message.user_headers = None;
            } else {
                let wire = user_headers_to_wire(&headers_map);
                msg.message.header.user_headers_length = wire.as_bytes().len() as u32;
                msg.message.user_headers = Some(wire.into_bytes());
            };
        }
    }
    Ok(())
}
