// This example demonstrates how to set up a rtsp server using GStreamer.
// While the "rtsp-server" example is about streaming media to connecting
// clients, this example is mainly about recording media that clients
// send to the server. For this, the launch syntax pipeline, that is passed
// to this example's cli is spawned and the client's media is streamed into it.

use std::{env, ptr};

use anyhow::Error;
use derive_more::{Display, Error};
use glib::translate::*;
use gst_rtsp_server::prelude::*;

#[path = "../examples-common.rs"]
mod examples_common;

#[derive(Debug, Display, Error)]
#[display(fmt = "Could not get mount points")]
struct NoMountPoints;

#[derive(Debug, Display, Error)]
#[display(fmt = "Usage: {_0} LAUNCH_LINE")]
struct UsageError(#[error(not(source))] String);

fn main_loop() -> Result<(), Error> {
    let args: Vec<_> = env::args().collect();

    if args.len() != 2 {
        return Err(Error::from(UsageError(args[0].clone())));
    }

    // Mostly analog to the rtsp-server example, the server is created
    // and the factory for our test mount is configured.
    let main_loop = glib::MainLoop::new(None, false);
    let server = gst_rtsp_server::RTSPServer::new();
    // Much like HTTP servers, RTSP servers have multiple endpoints that
    // provide or take different streams. Here, we ask our server to give
    // us a reference to its list of endpoints, so we can add our
    // test endpoint.
    let mounts = server.mount_points().ok_or(NoMountPoints)?;
    // Next, we create a factory for the endpoint we want to create.
    // The job of the factory is to create a new pipeline for each client that
    // connects, or (if configured to do so) to reuse an existing pipeline.
    let factory = gst_rtsp_server::RTSPMediaFactory::new();
    // Here we configure a method of authentication that we want the
    // server to require from clients.
    let auth = gst_rtsp_server::RTSPAuth::new();
    let token = gst_rtsp_server::RTSPToken::new(&[(
        gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE,
        &"user",
    )]);
    let basic = gst_rtsp_server::RTSPAuth::make_basic("user", "password");
    // For proper authentication, we want to use encryption. And there's no
    // encryption without a certificate!
    let cert = gio::TlsCertificate::from_pem(
        "-----BEGIN CERTIFICATE-----\
         MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk\
         ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp\
         ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq\
         hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx\
         NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW\
         B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3\
         DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf\
         hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC\
         MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1\
         Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk\
         PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH\
         QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4=\
         -----END CERTIFICATE-----\
         -----BEGIN PRIVATE KEY-----\
         MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA2EnE8ZOeVwZmwzPc\
         88DvoK1ckhOK7nVrsx9j6TmyKJ6m34UaAGpSlZbhvY72xyPNXl8QnUjm79SgT9bG\
         zeUc6QIDAQABAkBRFJZ32VbqWMP9OVwDJLiwC01AlYLnka0mIQZbT/2xq9dUc9GW\
         U3kiVw4lL8v/+sPjtTPCYYdzHHOyDen6znVhAiEA9qJT7BtQvRxCvGrAhr9MS022\
         tTdPbW829BoUtIeH64cCIQDggG5i48v7HPacPBIH1RaSVhXl8qHCpQD3qrIw3FMw\
         DwIga8PqH5Sf5sHedy2+CiK0V4MRfoU4c3zQ6kArI+bEgSkCIQCLA1vXBiE31B5s\
         bdHoYa1BXebfZVd+1Hd95IfEM5mbRwIgSkDuQwV55BBlvWph3U8wVIMIb4GStaH8\
         W535W8UBbEg=-----END PRIVATE KEY-----",
    )?;

    // Bindable versions were added in b1f515178a363df0322d7adbd5754e1f6e2083c9
    // This declares that the user "user" (once authenticated) has a role that
    // allows them to access and construct media factories.
    unsafe {
        gst_rtsp_server::ffi::gst_rtsp_media_factory_add_role(
            factory.to_glib_none().0,
            "user".to_glib_none().0,
            gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_ACCESS
                .to_glib_none()
                .0,
            <bool as StaticType>::static_type().into_glib() as *const u8,
            true.into_glib() as *const u8,
            gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_CONSTRUCT.as_ptr() as *const u8,
            <bool as StaticType>::static_type().into_glib() as *const u8,
            true.into_glib() as *const u8,
            ptr::null_mut::<u8>(),
        );
    }

    auth.set_tls_certificate(Some(&cert));
    auth.add_basic(basic.as_str(), &token);
    // Here, we tell the RTSP server about the authentication method we
    // configured above.
    server.set_auth(Some(&auth));

    factory.set_launch(args[1].as_str());
    // Tell the RTSP server that we want to work in RECORD mode (clients send)
    // data to us.
    factory.set_transport_mode(gst_rtsp_server::RTSPTransportMode::RECORD);
    // The RTSP protocol allows a couple of different profiles for the actually
    // used protocol of data-transmission. With this, we can limit the selection
    // from which connecting clients have to choose.
    // SAVP/SAVPF are via SRTP (encrypted), that's what the S is for.
    // The F in the end is for feedback (an extension that allows more bidirectional
    // feedback between sender and receiver). AV is just Audio/Video, P is Profile :)
    // The default, old RTP profile is AVP
    factory.set_profiles(gst_rtsp::RTSPProfile::SAVP | gst_rtsp::RTSPProfile::SAVPF);

    // Now we add a new mount-point and tell the RTSP server to use the factory
    // we configured beforehand. This factory will take on the job of creating
    // a pipeline, which will take on the incoming data of connected clients.
    mounts.add_factory("/test", factory);

    // Attach the server to our main context.
    // A main context is the thing where other stuff is registering itself for its
    // events (e.g. sockets, GStreamer bus, ...) and the main loop is something that
    // polls the main context for its events and dispatches them to whoever is
    // interested in them. In this example, we only do have one, so we can
    // leave the context parameter empty, it will automatically select
    // the default one.
    let id = server.attach(None)?;

    println!(
        "Stream ready at rtsps://127.0.0.1:{}/test",
        server.bound_port()
    );

    // Start the mainloop. From this point on, the server will start to take
    // incoming connections from clients.
    main_loop.run();

    id.remove();

    Ok(())
}

fn example_main() -> Result<(), Error> {
    gst::init()?;
    main_loop()
}

fn main() {
    match examples_common::run(example_main) {
        Ok(r) => r,
        Err(e) => eprintln!("Error! {e}"),
    }
}
