//! Transport module
use crate::error::Error;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use std::path::{Path, PathBuf};

pub struct Transport(PyObject);

impl Transport {
    pub fn new(obj: PyObject) -> Self {
        Transport(obj)
    }

    pub fn base(&self) -> url::Url {
        pyo3::Python::with_gil(|py| {
            self.to_object(py)
                .getattr(py, "base")
                .unwrap()
                .extract::<String>(py)
                .unwrap()
                .parse()
                .unwrap()
        })
    }

    pub fn is_local(&self) -> bool {
        pyo3::import_exception!(breezy.errors, NotLocalUrl);
        pyo3::Python::with_gil(|py| {
            self.0
                .call_method1(py, "local_abspath", (".",))
                .map(|_| true)
                .or_else(|e| {
                    if e.is_instance_of::<NotLocalUrl>(py) {
                        Ok::<_, PyErr>(false)
                    } else {
                        panic!("Unexpected error: {:?}", e)
                    }
                })
                .unwrap()
        })
    }

    pub fn local_abspath(&self, path: &Path) -> Result<PathBuf, Error> {
        pyo3::Python::with_gil(|py| {
            Ok(self
                .0
                .call_method1(py, "local_abspath", (path,))
                .unwrap()
                .extract::<PathBuf>(py)
                .unwrap())
        })
    }

    pub fn has(&self, path: &str) -> Result<bool, Error> {
        pyo3::Python::with_gil(|py| {
            Ok(self
                .0
                .call_method1(py, "has", (path,))?
                .extract::<bool>(py)
                .unwrap())
        })
    }

    pub fn ensure_base(&self) -> Result<(), Error> {
        pyo3::Python::with_gil(|py| {
            self.0.call_method0(py, "ensure_base")?;
            Ok(())
        })
    }

    pub fn create_prefix(&self) -> Result<(), Error> {
        pyo3::Python::with_gil(|py| {
            self.0.call_method0(py, "create_prefix")?;
            Ok(())
        })
    }

    pub fn clone(&self, path: &str) -> Result<Transport, Error> {
        pyo3::Python::with_gil(|py| {
            let o = self.0.call_method1(py, "clone", (path,))?;
            Ok(Transport(o.to_object(py)))
        })
    }
}

impl FromPyObject<'_> for Transport {
    fn extract_bound(obj: &Bound<PyAny>) -> PyResult<Self> {
        Ok(Transport(obj.to_object(obj.py())))
    }
}

impl ToPyObject for Transport {
    fn to_object(&self, py: Python) -> PyObject {
        self.0.to_object(py)
    }
}

pub fn get_transport(
    url: &url::Url,
    possible_transports: Option<&mut Vec<Transport>>,
) -> Result<Transport, Error> {
    pyo3::Python::with_gil(|py| {
        let urlutils = py.import_bound("breezy.transport").unwrap();
        let kwargs = PyDict::new_bound(py);
        kwargs.set_item(
            "possible_transports",
            possible_transports.map(|t| {
                t.iter()
                    .map(|t| t.0.clone_ref(py))
                    .collect::<Vec<PyObject>>()
            }),
        )?;
        let o = urlutils.call_method("get_transport", (url.to_string(),), Some(&kwargs))?;
        Ok(Transport(o.to_object(py)))
    })
}
