mod proxy;
mod server;
mod client;
mod describe;
mod reader;
mod opcodes;

use anyhow::Result;
use argh::FromArgs;
use std::net::SocketAddr;
use std::path::PathBuf;

#[derive(FromArgs)]
/// tcptape: record and replay TCP traffic
struct TopLevel {
    #[argh(subcommand)]
    command: Command,
}

#[derive(FromArgs)]
#[argh(subcommand)]
enum Command {
    Proxy(ProxyCmd),
    Server(ServerCmd),
    Client(ClientCmd),
    Describe(DescribeCmd),
}

#[derive(FromArgs)]
/// proxy TCP traffic from one address to another and save all forwarded traffic to a file
#[argh(subcommand, name = "proxy")]
struct ProxyCmd {
    #[argh(positional)]
    /// socket address to listen on
    listen: SocketAddr,

    #[argh(positional)]
    /// socket address to connect to
    connect: SocketAddr,

    #[argh(positional)]
    /// file to write traffic to
    file: PathBuf,
}

#[derive(FromArgs)]
/// start a server that verifies all incoming traffic based on specified file and replies based on the recorded data from the file
#[argh(subcommand, name = "server")]
struct ServerCmd {
    #[argh(positional)]
    /// socket address to listen on
    listen: SocketAddr,

    #[argh(positional)]
    /// file to read initial data from
    file: PathBuf,

    #[argh(switch, short = 'n')]
    /// just count bytes, don't assert
    no_assert: bool,

    #[argh(switch, short = 'd')]
    /// apply delays before replaying
    delay: bool,

    #[argh(switch, short = 'a')]
    /// exit with code 3 on a mismatch
    abort: bool,

    #[argh(switch)]
    /// serve only once
    once: bool,

    #[argh(switch)]
    /// show fancy text diff on a mismatch
    text_diff: bool,
}

#[derive(FromArgs)]
/// connect to a server, verifying all incoming traffic based on specified file and replying based on the recorded data from the file
#[argh(subcommand, name = "client")]
struct ClientCmd {
    #[argh(positional)]
    /// socket address to connect to
    connect: SocketAddr,

    #[argh(positional)]
    /// file to read data from
    file: PathBuf,

    #[argh(switch, short = 'n')]
    /// just count bytes, don't assert
    no_assert: bool,

    #[argh(switch, short = 'd')]
    /// apply delays before replaying
    delay: bool,

    #[argh(switch)]
    /// show fancy text diff on mismatch
    text_diff: bool,
}

#[derive(FromArgs)]
/// describe tcptape file contents
#[argh(subcommand, name = "describe")]
struct DescribeCmd {
    #[argh(positional)]
    /// file to describe
    file: PathBuf,

    #[argh(switch, short = 'v')]
    /// print hex-encoded buffer contents
    verbose: bool,
}

#[tokio::main(flavor="current_thread")]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .init();

    let top: TopLevel = argh::from_env();

    match top.command {
        Command::Proxy(cmd) => proxy::run(cmd.listen, cmd.connect, cmd.file).await?,
        Command::Server(cmd) => server::run(cmd.listen, cmd.file, cmd.no_assert, cmd.delay, cmd.abort, cmd.once, cmd.text_diff).await?,
        Command::Client(cmd) => client::run(cmd.connect, cmd.file, cmd.no_assert, cmd.delay, cmd.text_diff).await?,
        Command::Describe(cmd) => describe::run(cmd.file, cmd.verbose)?,
    }

    Ok(())
}
