use log::{debug, error, warn};
use parking_lot::ReentrantMutex;
use std::ffi::OsString;
use std::fmt::Debug;
use std::path::PathBuf;
use std::process::Child;
use std::time::Duration;
use std::{ffi::CString, path::Path, sync::Arc};
pub use crate::{
    asset::AssetLibrary,
    errors::*,
    ffi::{
        enums::*, CompositorOptions, CookOptions, ImageFileFormat, SessionInfo, SessionSyncInfo,
        ThriftServerOptions, TimelineOptions, Viewport,
    },
    node::{HoudiniNode, ManagerNode, ManagerType, NodeHandle, NodeType, Transform},
    parameter::Parameter,
    stringhandle::StringArray,
};
pub type SessionState = State;
use crate::ffi::ImageInfo;
use crate::stringhandle::StringHandle;
use crate::{ffi::raw, utils};
pub struct NodeBuilder<'s> {
    session: &'s Session,
    name: String,
    label: Option<String>,
    parent: Option<NodeHandle>,
    cook: bool,
}
impl<'s> NodeBuilder<'s> {
    pub fn with_label(mut self, label: impl Into<String>) -> Self {
        self.label = Some(label.into());
        self
    }
    pub fn with_parent<H: AsRef<NodeHandle>>(mut self, parent: H) -> Self {
        self.parent.replace(*parent.as_ref());
        self
    }
    pub fn cook(mut self, cook: bool) -> Self {
        self.cook = cook;
        self
    }
    pub fn create(self) -> Result<HoudiniNode> {
        let NodeBuilder {
            session,
            name,
            label,
            parent,
            cook,
        } = self;
        session.create_node_with(&name, parent, label.as_deref(), cook)
    }
}
impl PartialEq for raw::HAPI_Session {
    fn eq(&self, other: &Self) -> bool {
        self.type_ == other.type_ && self.id == other.id
    }
}
pub trait EnvVariable {
    type Type: ?Sized + ToOwned + Debug;
    fn get_value(session: &Session, key: impl AsRef<str>)
        -> Result<<Self::Type as ToOwned>::Owned>;
    fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()>;
}
impl EnvVariable for str {
    type Type = str;
    fn get_value(session: &Session, key: impl AsRef<str>) -> Result<String> {
        let key = CString::new(key.as_ref())?;
        let handle = crate::ffi::get_server_env_str(session, &key)?;
        crate::stringhandle::get_string(handle, session)
    }
    fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
        let key = CString::new(key.as_ref())?;
        let val = CString::new(val)?;
        crate::ffi::set_server_env_str(session, &key, &val)
    }
}
impl EnvVariable for Path {
    type Type = Self;
    fn get_value(session: &Session, key: impl AsRef<str>) -> Result<PathBuf> {
        let key = CString::new(key.as_ref())?;
        crate::stringhandle::get_string(crate::ffi::get_server_env_str(session, &key)?, session)
            .map(PathBuf::from)
    }
    fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
        let key = CString::new(key.as_ref())?;
        let val = utils::path_to_cstring(val)?;
        crate::ffi::set_server_env_str(session, &key, &val)
    }
}
impl EnvVariable for i32 {
    type Type = Self;
    fn get_value(session: &Session, key: impl AsRef<str>) -> Result<Self::Type> {
        let key = CString::new(key.as_ref())?;
        crate::ffi::get_server_env_int(session, &key)
    }
    fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
        let key = CString::new(key.as_ref())?;
        crate::ffi::set_server_env_int(session, &key, *val)
    }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum CookResult {
    Succeeded,
    Warnings,
    Errored(String),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ConnectionType {
    ThriftPipe(OsString),
    ThriftSocket(std::net::SocketAddrV4),
    SharedMemory(String),
    InProcess,
    Custom,
}
#[derive(Debug)]
pub(crate) struct SessionInner {
    pub(crate) handle: raw::HAPI_Session,
    pub(crate) options: SessionOptions,
    pub(crate) connection: ConnectionType,
    pub(crate) pid: Option<u32>,
    pub(crate) lock: ReentrantMutex<()>,
}
#[derive(Debug, Clone)]
pub struct Session {
    pub(crate) inner: Arc<SessionInner>,
}
impl PartialEq for Session {
    fn eq(&self, other: &Self) -> bool {
        self.inner.handle.id == other.inner.handle.id
            && self.inner.handle.type_ == other.inner.handle.type_
    }
}
impl Session {
    fn new(
        handle: raw::HAPI_Session,
        connection: ConnectionType,
        options: SessionOptions,
        pid: Option<u32>,
    ) -> Session {
        Session {
            inner: Arc::new(SessionInner {
                handle,
                options,
                connection,
                lock: ReentrantMutex::new(()),
                pid,
            }),
        }
    }
    pub fn session_type(&self) -> SessionType {
        self.inner.handle.type_
    }
    pub fn connection_type(&self) -> &ConnectionType {
        &self.inner.connection
    }
    pub fn server_pid(&self) -> Option<u32> {
        self.inner.pid
    }
    #[inline(always)]
    pub(crate) fn ptr(&self) -> *const raw::HAPI_Session {
        &(self.inner.handle) as *const _
    }
    pub fn set_server_var<T: EnvVariable + ?Sized>(
        &self,
        key: &str,
        value: &T::Type,
    ) -> Result<()> {
        debug_assert!(self.is_valid());
        debug!("Setting server variable {key}={value:?}");
        T::set_value(self, key, value)
    }
    pub fn get_server_var<T: EnvVariable + ?Sized>(
        &self,
        key: &str,
    ) -> Result<<T::Type as ToOwned>::Owned> {
        debug_assert!(self.is_valid());
        T::get_value(self, key)
    }
    pub fn get_server_variables(&self) -> Result<StringArray> {
        debug_assert!(self.is_valid());
        let count = crate::ffi::get_server_env_var_count(self)?;
        let handles = crate::ffi::get_server_env_var_list(self, count)?;
        crate::stringhandle::get_string_array(&handles, self).context("Calling get_string_array")
    }
    pub fn get_string(&self, handle: StringHandle) -> Result<String> {
        crate::stringhandle::get_string(handle, self)
    }
    pub fn get_string_batch(&self, handles: &[StringHandle]) -> Result<StringArray> {
        crate::stringhandle::get_string_array(handles, self)
    }
    fn initialize(&self) -> Result<()> {
        debug!("Initializing session");
        debug_assert!(self.is_valid());
        let res = crate::ffi::initialize_session(self, &self.inner.options);
        match res {
            Ok(_) => Ok(()),
            Err(HapiError {
                kind: Kind::Hapi(HapiResult::AlreadyInitialized),
                ..
            }) => {
                warn!("Session already initialized, skipping");
                Ok(())
            }
            Err(e) => Err(e),
        }
    }
    pub fn cleanup(&self) -> Result<()> {
        debug!("Cleaning session");
        debug_assert!(self.is_valid());
        crate::ffi::cleanup_session(self)
    }
    pub fn is_initialized(&self) -> bool {
        debug_assert!(self.is_valid());
        crate::ffi::is_session_initialized(self)
    }
    pub fn create_input_node(
        &self,
        name: &str,
        parent: Option<NodeHandle>,
    ) -> Result<crate::geometry::Geometry> {
        debug!("Creating input node: {}", name);
        debug_assert!(self.is_valid());
        let name = CString::new(name)?;
        let id = crate::ffi::create_input_node(self, &name, parent)?;
        let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
        let info = crate::geometry::GeoInfo::from_node(&node)?;
        Ok(crate::geometry::Geometry { node, info })
    }
    pub fn create_input_curve_node(
        &self,
        name: &str,
        parent: Option<NodeHandle>,
    ) -> Result<crate::geometry::Geometry> {
        debug!("Creating input curve node: {}", name);
        debug_assert!(self.is_valid());
        let name = CString::new(name)?;
        let id = crate::ffi::create_input_curve_node(self, &name, parent)?;
        let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
        let info = crate::geometry::GeoInfo::from_node(&node)?;
        Ok(crate::geometry::Geometry { node, info })
    }
    pub fn create_node(&self, name: impl AsRef<str>) -> Result<HoudiniNode> {
        self.create_node_with(name.as_ref(), None, None, false)
    }
    pub fn node_builder(&self, node_name: impl Into<String>) -> NodeBuilder {
        NodeBuilder {
            session: self,
            name: node_name.into(),
            label: None,
            parent: None,
            cook: false,
        }
    }
    pub(crate) fn create_node_with<P>(
        &self,
        name: &str,
        parent: P,
        label: Option<&str>,
        cook: bool,
    ) -> Result<HoudiniNode>
    where
        P: Into<Option<NodeHandle>>,
    {
        let parent = parent.into();
        debug!("Creating node instance: {}", name);
        debug_assert!(self.is_valid());
        debug_assert!(
            parent.is_some() || name.contains('/'),
            "Node name must be fully qualified if parent is not specified"
        );
        debug_assert!(
            !(parent.is_some() && name.contains('/')),
            "Cannot use fully qualified node name with parent"
        );
        let name = CString::new(name)?;
        let label = label.map(CString::new).transpose()?;
        let id = crate::ffi::create_node(&name, label.as_deref(), self, parent, cook)?;
        HoudiniNode::new(self.clone(), NodeHandle(id), None)
    }
    pub fn delete_node<H: Into<NodeHandle>>(&self, node: H) -> Result<()> {
        crate::ffi::delete_node(node.into(), self)
    }
    pub fn get_node_from_path(
        &self,
        path: impl AsRef<str>,
        parent: impl Into<Option<NodeHandle>>,
    ) -> Result<Option<HoudiniNode>> {
        debug_assert!(self.is_valid());
        debug!("Searching node at path: {}", path.as_ref());
        let path = CString::new(path.as_ref())?;
        match crate::ffi::get_node_from_path(self, parent.into(), &path) {
            Ok(handle) => Ok(NodeHandle(handle).to_node(self).ok()),
            Err(HapiError {
                kind: Kind::Hapi(HapiResult::InvalidArgument),
                ..
            }) => Ok(None),
            Err(e) => Err(e),
        }
    }
    pub fn find_parameter_from_path(
        &self,
        path: impl AsRef<str>,
        start: impl Into<Option<NodeHandle>>,
    ) -> Result<Option<Parameter>> {
        debug_assert!(self.is_valid());
        debug!("Searching parameter at path: {}", path.as_ref());
        let Some((path, parm)) = path.as_ref().rsplit_once('/') else {
            return Ok(None);
        };
        let Some(node) = self.get_node_from_path(path, start)? else {
            debug!("Node {} not found", path);
            return Ok(None);
        };
        Ok(node.parameter(parm).ok())
    }
    pub fn get_manager_node(&self, manager: ManagerType) -> Result<ManagerNode> {
        debug_assert!(self.is_valid());
        debug!("Getting Manager node of type: {:?}", manager);
        let node_type = NodeType::from(manager);
        let handle = crate::ffi::get_manager_node(self, node_type)?;
        Ok(ManagerNode {
            session: self.clone(),
            handle: NodeHandle(handle),
            node_type: manager,
        })
    }
    pub fn get_composed_object_transform(
        &self,
        parent: impl AsRef<NodeHandle>,
        rst_order: RSTOrder,
    ) -> Result<Vec<Transform>> {
        debug_assert!(self.is_valid());
        crate::ffi::get_composed_object_transforms(self, *parent.as_ref(), rst_order)
            .map(|transforms| transforms.into_iter().map(Transform).collect())
    }
    pub fn save_hip(&self, path: impl AsRef<Path>, lock_nodes: bool) -> Result<()> {
        debug!("Saving hip file: {:?}", path.as_ref());
        debug_assert!(self.is_valid());
        let path = utils::path_to_cstring(path)?;
        crate::ffi::save_hip(self, &path, lock_nodes)
    }
    pub fn load_hip(&self, path: impl AsRef<Path>, cook: bool) -> Result<()> {
        debug!("Loading hip file: {:?}", path.as_ref());
        debug_assert!(self.is_valid());
        let path = utils::path_to_cstring(path)?;
        crate::ffi::load_hip(self, &path, cook)
    }
    pub fn merge_hip(&self, name: &str, cook: bool) -> Result<i32> {
        debug!("Merging hip file: {}", name);
        debug_assert!(self.is_valid());
        let name = CString::new(name)?;
        crate::ffi::merge_hip(self, &name, cook)
    }
    pub fn get_hip_file_nodes(&self, hip_id: i32) -> Result<Vec<NodeHandle>> {
        crate::ffi::get_hipfile_node_ids(self, hip_id)
            .map(|handles| handles.into_iter().map(NodeHandle).collect())
    }
    pub fn load_asset_file(&self, file: impl AsRef<Path>) -> Result<AssetLibrary> {
        debug_assert!(self.is_valid());
        AssetLibrary::from_file(self.clone(), file)
    }
    pub fn get_loaded_asset_libraries(&self) -> Result<Vec<AssetLibrary>> {
        debug_assert!(self.is_valid());
        crate::ffi::get_asset_library_ids(self)?
            .into_iter()
            .map(|library_id| {
                crate::ffi::get_asset_library_file_path(self, library_id).map(|lib_file| {
                    AssetLibrary {
                        lib_id: library_id,
                        session: self.clone(),
                        file: Some(PathBuf::from(lib_file)),
                    }
                })
            })
            .collect::<Result<Vec<_>>>()
    }
    pub fn interrupt(&self) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::interrupt(self)
    }
    pub fn get_status(&self, flag: StatusType) -> Result<SessionState> {
        debug_assert!(self.is_valid());
        crate::ffi::get_status(self, flag)
    }
    pub fn is_cooking(&self) -> Result<bool> {
        debug_assert!(self.is_valid());
        Ok(matches!(
            self.get_status(StatusType::CookState)?,
            SessionState::Cooking
        ))
    }
    #[inline(always)]
    pub fn is_valid(&self) -> bool {
        crate::ffi::is_session_valid(self)
    }
    pub fn get_status_string(
        &self,
        status: StatusType,
        verbosity: StatusVerbosity,
    ) -> Result<String> {
        debug_assert!(self.is_valid());
        crate::ffi::get_status_string(self, status, verbosity)
    }
    pub fn get_cook_result_string(&self, verbosity: StatusVerbosity) -> Result<String> {
        debug_assert!(self.is_valid());
        self.get_status_string(StatusType::CookResult, verbosity)
    }
    pub fn cooking_total_count(&self) -> Result<i32> {
        debug_assert!(self.is_valid());
        crate::ffi::get_cooking_total_count(self)
    }
    pub fn cooking_current_count(&self) -> Result<i32> {
        debug_assert!(self.is_valid());
        crate::ffi::get_cooking_current_count(self)
    }
    pub fn cook(&self) -> Result<CookResult> {
        debug_assert!(self.is_valid());
        debug!("Cooking session..");
        if self.inner.options.threaded {
            loop {
                match self.get_status(StatusType::CookState)? {
                    SessionState::Ready => break Ok(CookResult::Succeeded),
                    SessionState::ReadyWithFatalErrors => {
                        self.interrupt()?;
                        let err = self.get_cook_result_string(StatusVerbosity::Errors)?;
                        break Ok(CookResult::Errored(err));
                    }
                    SessionState::ReadyWithCookErrors => break Ok(CookResult::Warnings),
                    _ => {}
                }
            }
        } else {
            Ok(CookResult::Succeeded)
        }
    }
    pub fn get_connection_error(&self, clear: bool) -> Result<String> {
        debug_assert!(self.is_valid());
        crate::ffi::get_connection_error(clear)
    }
    pub fn get_time(&self) -> Result<f32> {
        debug_assert!(self.is_valid());
        crate::ffi::get_time(self)
    }
    pub fn set_time(&self, time: f32) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::set_time(self, time)
    }
    pub fn lock(&self) -> parking_lot::ReentrantMutexGuard<()> {
        self.inner.lock.lock()
    }
    pub fn set_timeline_options(&self, options: TimelineOptions) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::set_timeline_options(self, &options.0)
    }
    pub fn get_timeline_options(&self) -> Result<TimelineOptions> {
        debug_assert!(self.is_valid());
        crate::ffi::get_timeline_options(self).map(TimelineOptions)
    }
    pub fn set_use_houdini_time(&self, do_use: bool) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::set_use_houdini_time(self, do_use)
    }
    pub fn get_use_houdini_time(&self) -> Result<bool> {
        debug_assert!(self.is_valid());
        crate::ffi::get_use_houdini_time(self)
    }
    pub fn get_viewport(&self) -> Result<Viewport> {
        debug_assert!(self.is_valid());
        crate::ffi::get_viewport(self).map(Viewport)
    }
    pub fn set_viewport(&self, viewport: &Viewport) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::set_viewport(self, viewport)
    }
    pub fn set_sync(&self, enable: bool) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::set_session_sync(self, enable)
    }
    pub fn get_sync_info(&self) -> Result<SessionSyncInfo> {
        debug_assert!(self.is_valid());
        crate::ffi::get_session_sync_info(self).map(SessionSyncInfo)
    }
    pub fn set_sync_info(&self, info: &SessionSyncInfo) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::set_session_sync_info(self, &info.0)
    }
    pub fn get_license_type(&self) -> Result<License> {
        debug_assert!(self.is_valid());
        crate::ffi::session_get_license_type(self)
    }
    pub fn render_cop_to_image(
        &self,
        cop_node: impl Into<NodeHandle>,
        image_planes: impl AsRef<str>,
        path: impl AsRef<Path>,
    ) -> Result<String> {
        debug!("Start rendering COP to image.");
        let cop_node = cop_node.into();
        debug_assert!(cop_node.is_valid(self)?);
        crate::ffi::render_cop_to_image(self, cop_node)?;
        crate::material::extract_image_to_file(self, cop_node, image_planes, path)
    }
    pub fn render_texture_to_image(
        &self,
        node: impl Into<NodeHandle>,
        parm_name: &str,
    ) -> Result<()> {
        debug_assert!(self.is_valid());
        let name = CString::new(parm_name)?;
        let node = node.into();
        let id = crate::ffi::get_parm_id_from_name(&name, node, self)?;
        crate::ffi::render_texture_to_image(&self, node, crate::parameter::ParmHandle(id))
    }
    pub fn extract_image_to_file(
        &self,
        node: impl Into<NodeHandle>,
        image_planes: &str,
        path: impl AsRef<Path>,
    ) -> Result<String> {
        crate::material::extract_image_to_file(self, node.into(), image_planes, path)
    }
    pub fn extract_image_to_memory(
        &self,
        node: impl Into<NodeHandle>,
        buffer: &mut Vec<u8>,
        image_planes: impl AsRef<str>,
        format: impl AsRef<str>,
    ) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::material::extract_image_to_memory(self, node.into(), buffer, image_planes, format)
    }
    pub fn get_image_info(&self, node: impl Into<NodeHandle>) -> Result<ImageInfo> {
        debug_assert!(self.is_valid());
        crate::ffi::get_image_info(self, node.into()).map(ImageInfo)
    }
    pub fn render_cop_to_memory(
        &self,
        cop_node: impl Into<NodeHandle>,
        buffer: &mut Vec<u8>,
        image_planes: impl AsRef<str>,
        format: impl AsRef<str>,
    ) -> Result<()> {
        debug!("Start rendering COP to memory.");
        let cop_node = cop_node.into();
        debug_assert!(cop_node.is_valid(self)?);
        crate::ffi::render_cop_to_image(self, cop_node)?;
        crate::material::extract_image_to_memory(self, cop_node, buffer, image_planes, format)
    }
    pub fn get_supported_image_formats(&self) -> Result<Vec<ImageFileFormat<'_>>> {
        debug_assert!(self.is_valid());
        crate::ffi::get_supported_image_file_formats(self).map(|v| {
            v.into_iter()
                .map(|inner| ImageFileFormat(inner, self.into()))
                .collect()
        })
    }
    pub fn get_active_cache_names(&self) -> Result<StringArray> {
        debug_assert!(self.is_valid());
        crate::ffi::get_active_cache_names(self)
    }
    pub fn get_cache_property_value(
        &self,
        cache_name: &str,
        property: CacheProperty,
    ) -> Result<i32> {
        let cache_name = CString::new(cache_name)?;
        crate::ffi::get_cache_property(self, &cache_name, property)
    }
    pub fn set_cache_property_value(
        &self,
        cache_name: &str,
        property: CacheProperty,
        value: i32,
    ) -> Result<()> {
        let cache_name = CString::new(cache_name)?;
        crate::ffi::set_cache_property(self, &cache_name, property, value)
    }
    pub fn python_thread_interpreter_lock(&self, lock: bool) -> Result<()> {
        debug_assert!(self.is_valid());
        crate::ffi::python_thread_interpreter_lock(self, lock)
    }
    pub fn get_compositor_options(&self) -> Result<CompositorOptions> {
        crate::ffi::get_compositor_options(self).map(CompositorOptions)
    }
    pub fn set_compositor_options(&self, options: &CompositorOptions) -> Result<()> {
        crate::ffi::set_compositor_options(self, &options.0)
    }
    pub fn get_preset_names(&self, bytes: &[u8]) -> Result<Vec<String>> {
        debug_assert!(self.is_valid());
        let mut handles = vec![];
        for handle in crate::ffi::get_preset_names(self, bytes)? {
            let v = crate::stringhandle::get_string(handle, self)?;
            handles.push(v);
        }
        Ok(handles)
    }
    pub fn start_performance_monitor_profile(&self, title: &str) -> Result<i32> {
        let title = CString::new(title)?;
        crate::ffi::start_performance_monitor_profile(self, &title)
    }
    pub fn stop_performance_monitor_profile(
        &self,
        profile_id: i32,
        output_file: &str,
    ) -> Result<()> {
        let output_file = CString::new(output_file)?;
        crate::ffi::stop_performance_monitor_profile(self, profile_id, &output_file)
    }
    pub fn get_job_status(&self, job_id: i32) -> Result<JobStatus> {
        crate::ffi::get_job_status(self, job_id)
    }
}
impl Drop for Session {
    fn drop(&mut self) {
        if Arc::strong_count(&self.inner) == 1 {
            debug!("Dropping session pid: {:?}", self.server_pid());
            if self.is_valid() {
                if self.inner.options.cleanup {
                    if let Err(e) = self.cleanup() {
                        error!("Session cleanup failed in Drop: {}", e);
                    }
                }
                if let Err(e) = crate::ffi::shutdown_session(self) {
                    error!("Could not shutdown session in Drop: {}", e);
                }
                if let Err(e) = crate::ffi::close_session(self) {
                    error!("Closing session failed in Drop: {}", e);
                }
            } else {
                debug!("Session was invalid in Drop!");
                if let ConnectionType::ThriftPipe(f) = &self.inner.connection {
                    let _ = std::fs::remove_file(f);
                }
            }
        }
    }
}
pub fn connect_to_pipe(
    pipe: impl AsRef<Path>,
    options: Option<&SessionOptions>,
    timeout: Option<Duration>,
    pid: Option<u32>,
) -> Result<Session> {
    debug!("Connecting to Thrift session: {:?}", pipe.as_ref());
    let c_str = utils::path_to_cstring(&pipe)?;
    let pipe = pipe.as_ref().as_os_str().to_os_string();
    let timeout = timeout.unwrap_or_default();
    let options = options.cloned().unwrap_or_default();
    let mut waited = Duration::from_secs(0);
    let wait_ms = Duration::from_millis(100);
    let handle = loop {
        let mut last_error = None;
        debug!("Trying to connect to pipe server");
        match crate::ffi::new_thrift_piped_session(&c_str, &options.session_info.0) {
            Ok(handle) => break handle,
            Err(e) => {
                last_error.replace(e);
                std::thread::sleep(wait_ms);
                waited += wait_ms;
            }
        }
        if waited > timeout {
            return Err(last_error.unwrap()).context("Connection timeout");
        }
    };
    let connection = ConnectionType::ThriftPipe(pipe);
    let session = Session::new(handle, connection, options, pid);
    session.initialize()?;
    Ok(session)
}
pub fn connect_to_memory_server(
    memory_name: &str,
    options: Option<&SessionOptions>,
    pid: Option<u32>,
) -> Result<Session> {
    let mem_name = String::from(memory_name);
    let mem_name_cstr = CString::new(memory_name)?;
    let options = options.cloned().unwrap_or_default();
    let handle =
        crate::ffi::new_thrift_shared_memory_session(&mem_name_cstr, &options.session_info.0)?;
    let connection = ConnectionType::SharedMemory(mem_name);
    let session = Session::new(handle, connection, options, pid);
    session.initialize()?;
    Ok(session)
}
pub fn connect_to_socket(
    addr: std::net::SocketAddrV4,
    options: Option<&SessionOptions>,
) -> Result<Session> {
    debug!("Connecting to socket server: {:?}", addr);
    let host = CString::new(addr.ip().to_string()).expect("SocketAddr->CString");
    let options = options.cloned().unwrap_or_default();
    let handle =
        crate::ffi::new_thrift_socket_session(addr.port() as i32, &host, &options.session_info.0)?;
    let connection = ConnectionType::ThriftSocket(addr);
    let session = Session::new(handle, connection, options, None);
    session.initialize()?;
    Ok(session)
}
pub fn new_in_process(options: Option<&SessionOptions>) -> Result<Session> {
    debug!("Creating new in-process session");
    let options = options.cloned().unwrap_or_default();
    let handle = crate::ffi::create_inprocess_session(&options.session_info.0)?;
    let connection = ConnectionType::InProcess;
    let session = Session::new(handle, connection, options, Some(std::process::id()));
    session.initialize()?;
    Ok(session)
}
#[derive(Clone, Debug)]
pub struct SessionOptions {
    pub cook_opt: CookOptions,
    pub session_info: SessionInfo,
    pub threaded: bool,
    pub cleanup: bool,
    pub ignore_already_init: bool,
    pub env_files: Option<CString>,
    pub env_variables: Option<Vec<(String, String)>>,
    pub otl_path: Option<CString>,
    pub dso_path: Option<CString>,
    pub img_dso_path: Option<CString>,
    pub aud_dso_path: Option<CString>,
}
impl Default for SessionOptions {
    fn default() -> Self {
        SessionOptions {
            cook_opt: CookOptions::default(),
            session_info: SessionInfo::default(),
            threaded: false,
            cleanup: false,
            ignore_already_init: true,
            env_files: None,
            env_variables: None,
            otl_path: None,
            dso_path: None,
            img_dso_path: None,
            aud_dso_path: None,
        }
    }
}
#[derive(Default)]
pub struct SessionOptionsBuilder {
    cook_opt: CookOptions,
    session_info: SessionInfo,
    threaded: bool,
    cleanup: bool,
    ignore_already_init: bool,
    env_variables: Option<Vec<(String, String)>>,
    env_files: Option<CString>,
    otl_path: Option<CString>,
    dso_path: Option<CString>,
    img_dso_path: Option<CString>,
    aud_dso_path: Option<CString>,
}
impl SessionOptionsBuilder {
    pub fn houdini_env_files<I>(mut self, files: I) -> Self
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let paths = utils::join_paths(files);
        self.env_files
            .replace(CString::new(paths).expect("Zero byte"));
        self
    }
    pub fn env_variables<'a, I, K, V>(mut self, variables: I) -> Self
    where
        I: Iterator<Item = &'a (K, V)>,
        K: ToString + 'a,
        V: ToString + 'a,
    {
        self.env_variables.replace(
            variables
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
        );
        self
    }
    pub fn otl_search_paths<I>(mut self, paths: I) -> Self
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let paths = utils::join_paths(paths);
        self.otl_path
            .replace(CString::new(paths).expect("Zero byte"));
        self
    }
    pub fn dso_search_paths<P>(mut self, paths: P) -> Self
    where
        P: IntoIterator,
        P::Item: AsRef<str>,
    {
        let paths = utils::join_paths(paths);
        self.dso_path
            .replace(CString::new(paths).expect("Zero byte"));
        self
    }
    pub fn image_search_paths<P>(mut self, paths: P) -> Self
    where
        P: IntoIterator,
        P::Item: AsRef<str>,
    {
        let paths = utils::join_paths(paths);
        self.img_dso_path
            .replace(CString::new(paths).expect("Zero byte"));
        self
    }
    pub fn audio_search_paths<P>(mut self, paths: P) -> Self
    where
        P: IntoIterator,
        P::Item: AsRef<str>,
    {
        let paths = utils::join_paths(paths);
        self.aud_dso_path
            .replace(CString::new(paths).expect("Zero byte"));
        self
    }
    pub fn ignore_already_init(mut self, ignore: bool) -> Self {
        self.ignore_already_init = ignore;
        self
    }
    pub fn cook_options(mut self, options: CookOptions) -> Self {
        self.cook_opt = options;
        self
    }
    pub fn session_info(mut self, info: SessionInfo) -> Self {
        self.session_info = info;
        self
    }
    pub fn threaded(mut self, threaded: bool) -> Self {
        self.threaded = threaded;
        self
    }
    pub fn cleanup_on_close(mut self, cleanup: bool) -> Self {
        self.cleanup = cleanup;
        self
    }
    pub fn build(mut self) -> SessionOptions {
        self.write_temp_env_file();
        SessionOptions {
            cook_opt: self.cook_opt,
            session_info: self.session_info,
            threaded: self.threaded,
            cleanup: self.cleanup,
            ignore_already_init: self.cleanup,
            env_files: self.env_files,
            env_variables: self.env_variables,
            otl_path: self.otl_path,
            dso_path: self.dso_path,
            img_dso_path: self.img_dso_path,
            aud_dso_path: self.aud_dso_path,
        }
    }
    fn write_temp_env_file(&mut self) {
        use std::io::Write;
        if let Some(ref env) = self.env_variables {
            let mut file = tempfile::Builder::new()
                .suffix("_hars.env")
                .tempfile()
                .expect("tempfile");
            for (k, v) in env.iter() {
                writeln!(file, "{}={}", k, v).expect("write to .env file");
            }
            let (_, tmp_file) = file.keep().expect("persistent tempfile");
            debug!(
                "Creating temporary environment file: {}",
                tmp_file.to_string_lossy()
            );
            let tmp_file = CString::new(tmp_file.to_string_lossy().to_string()).expect("null byte");
            if let Some(old) = &mut self.env_files {
                let mut bytes = old.as_bytes_with_nul().to_vec();
                bytes.extend(tmp_file.into_bytes_with_nul());
                self.env_files
                    .replace(unsafe { CString::from_vec_with_nul_unchecked(bytes) });
            } else {
                self.env_files.replace(tmp_file);
            }
        }
    }
}
impl SessionOptions {
    pub fn builder() -> SessionOptionsBuilder {
        SessionOptionsBuilder::default()
    }
}
impl From<i32> for SessionState {
    fn from(s: i32) -> Self {
        match s {
            0 => SessionState::Ready,
            1 => SessionState::ReadyWithFatalErrors,
            2 => SessionState::ReadyWithCookErrors,
            3 => SessionState::StartingCook,
            4 => SessionState::Cooking,
            5 => SessionState::StartingLoad,
            6 => SessionState::Loading,
            7 => SessionState::Max,
            _ => panic!("Unmatched SessionState - {s}"),
        }
    }
}
pub fn start_engine_pipe_server(
    path: impl AsRef<Path>,
    log_file: Option<&str>,
    options: &ThriftServerOptions,
) -> Result<u32> {
    debug!("Starting named pipe server: {:?}", path.as_ref());
    let log_file = log_file.map(CString::new).transpose()?;
    let c_str = utils::path_to_cstring(path)?;
    crate::ffi::clear_connection_error()?;
    crate::ffi::start_thrift_pipe_server(&c_str, &options.0, log_file.as_deref())
}
pub fn start_engine_socket_server(
    port: u16,
    log_file: Option<&str>,
    options: &ThriftServerOptions,
) -> Result<u32> {
    debug!("Starting socket server on port: {}", port);
    let log_file = log_file.map(CString::new).transpose()?;
    crate::ffi::clear_connection_error()?;
    crate::ffi::start_thrift_socket_server(port as i32, &options.0, log_file.as_deref())
}
pub fn start_houdini_server(
    pipe_name: impl AsRef<str>,
    houdini_executable: impl AsRef<Path>,
    fx_license: bool,
) -> Result<Child> {
    std::process::Command::new(houdini_executable.as_ref())
        .arg(format!("-hess=pipe:{}", pipe_name.as_ref()))
        .arg(if fx_license {
            "-force-fx-license"
        } else {
            "-core"
        })
        .stdin(std::process::Stdio::null())
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .spawn()
        .map_err(HapiError::from)
}
pub fn start_shared_memory_server(
    memory_name: &str,
    options: &ThriftServerOptions,
    log_file: Option<&str>,
) -> Result<u32> {
    debug!("Starting shared memory server name: {memory_name}");
    let memory_name = CString::new(memory_name)?;
    let log_file = log_file.map(CString::new).transpose()?;
    crate::ffi::clear_connection_error()?;
    crate::ffi::start_thrift_shared_memory_server(&memory_name, &options.0, log_file.as_deref())
}
pub fn quick_session(options: Option<&SessionOptions>) -> Result<Session> {
    let server_options = ThriftServerOptions::default()
        .with_auto_close(true)
        .with_timeout_ms(4000f32)
        .with_verbosity(StatusVerbosity::Statusverbosity1);
    let rand_memory_name = format!("shared-memory-{}", utils::random_string(16));
    let pid = start_shared_memory_server(&rand_memory_name, &server_options, None)?;
    connect_to_memory_server(&rand_memory_name, options, Some(pid))
}