/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed 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 crate::binder::Stability;
use crate::error::StatusCode;
use crate::parcel::{
    BorrowedParcel, Deserialize, Parcel, Parcelable, Serialize, NON_NULL_PARCELABLE_FLAG,
    NULL_PARCELABLE_FLAG,
};

use downcast_rs::{impl_downcast, DowncastSync};
use std::any::Any;
use std::sync::{Arc, Mutex};

/// Metadata that `ParcelableHolder` needs for all parcelables.
///
/// The compiler auto-generates implementations of this trait
/// for AIDL parcelables.
pub trait ParcelableMetadata {
    /// The Binder parcelable descriptor string.
    ///
    /// This string is a unique identifier for a Binder parcelable.
    fn get_descriptor() -> &'static str;

    /// The Binder parcelable stability.
    fn get_stability(&self) -> Stability {
        Stability::Local
    }
}

trait AnyParcelable: DowncastSync + Parcelable + std::fmt::Debug {}
impl_downcast!(sync AnyParcelable);
impl<T> AnyParcelable for T where T: DowncastSync + Parcelable + std::fmt::Debug {}

#[derive(Debug, Clone)]
enum ParcelableHolderData {
    Empty,
    Parcelable { parcelable: Arc<dyn AnyParcelable>, name: String },
    Parcel(Parcel),
}

/// A container that can hold any arbitrary `Parcelable`.
///
/// This type is currently used for AIDL parcelable fields.
///
/// `ParcelableHolder` is currently not thread-safe (neither
/// `Send` nor `Sync`), mainly because it internally contains
/// a `Parcel` which in turn is not thread-safe.
#[derive(Debug)]
pub struct ParcelableHolder {
    // This is a `Mutex` because of `get_parcelable`
    // which takes `&self` for consistency with C++.
    // We could make `get_parcelable` take a `&mut self`
    // and get rid of the `Mutex` here for a performance
    // improvement, but then callers would require a mutable
    // `ParcelableHolder` even for that getter method.
    data: Mutex<ParcelableHolderData>,
    stability: Stability,
}

impl ParcelableHolder {
    /// Construct a new `ParcelableHolder` with the given stability.
    pub fn new(stability: Stability) -> Self {
        Self { data: Mutex::new(ParcelableHolderData::Empty), stability }
    }

    /// Reset the contents of this `ParcelableHolder`.
    ///
    /// Note that this method does not reset the stability,
    /// only the contents.
    pub fn reset(&mut self) {
        *self.data.get_mut().unwrap() = ParcelableHolderData::Empty;
        // We could also clear stability here, but C++ doesn't
    }

    /// Set the parcelable contained in this `ParcelableHolder`.
    pub fn set_parcelable<T>(&mut self, p: Arc<T>) -> Result<(), StatusCode>
    where
        T: Any + Parcelable + ParcelableMetadata + std::fmt::Debug + Send + Sync,
    {
        if self.stability > p.get_stability() {
            return Err(StatusCode::BAD_VALUE);
        }

        *self.data.get_mut().unwrap() =
            ParcelableHolderData::Parcelable { parcelable: p, name: T::get_descriptor().into() };

        Ok(())
    }

    /// Retrieve the parcelable stored in this `ParcelableHolder`.
    ///
    /// This method attempts to retrieve the parcelable inside
    /// the current object as a parcelable of type `T`.
    /// The object is validated against `T` by checking that
    /// its parcelable descriptor matches the one returned
    /// by `T::get_descriptor()`.
    ///
    /// Returns one of the following:
    /// * `Err(_)` in case of error
    /// * `Ok(None)` if the holder is empty or the descriptor does not match
    /// * `Ok(Some(_))` if the object holds a parcelable of type `T`
    ///   with the correct descriptor
    pub fn get_parcelable<T>(&self) -> Result<Option<Arc<T>>, StatusCode>
    where
        T: Any + Parcelable + ParcelableMetadata + Default + std::fmt::Debug + Send + Sync,
    {
        let parcelable_desc = T::get_descriptor();
        let mut data = self.data.lock().unwrap();
        match *data {
            ParcelableHolderData::Empty => Ok(None),
            ParcelableHolderData::Parcelable { ref parcelable, ref name } => {
                if name != parcelable_desc {
                    return Err(StatusCode::BAD_VALUE);
                }

                match Arc::clone(parcelable).downcast_arc::<T>() {
                    Err(_) => Err(StatusCode::BAD_VALUE),
                    Ok(x) => Ok(Some(x)),
                }
            }
            ParcelableHolderData::Parcel(ref mut parcel) => {
                // Safety: 0 should always be a valid position.
                unsafe {
                    parcel.set_data_position(0)?;
                }

                let name: String = parcel.read()?;
                if name != parcelable_desc {
                    return Ok(None);
                }

                let mut parcelable = T::default();
                parcelable.read_from_parcel(parcel.borrowed_ref())?;

                let parcelable = Arc::new(parcelable);
                let result = Arc::clone(&parcelable);
                *data = ParcelableHolderData::Parcelable { parcelable, name };

                Ok(Some(result))
            }
        }
    }

    /// Return the stability value of this object.
    pub fn get_stability(&self) -> Stability {
        self.stability
    }
}

impl Clone for ParcelableHolder {
    fn clone(&self) -> ParcelableHolder {
        ParcelableHolder {
            data: Mutex::new(self.data.lock().unwrap().clone()),
            stability: self.stability,
        }
    }
}

impl Serialize for ParcelableHolder {
    fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
        parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
        self.write_to_parcel(parcel)
    }
}

impl Deserialize for ParcelableHolder {
    type UninitType = Self;
    fn uninit() -> Self::UninitType {
        Self::new(Default::default())
    }
    fn from_init(value: Self) -> Self::UninitType {
        value
    }

    fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self, StatusCode> {
        let status: i32 = parcel.read()?;
        if status == NULL_PARCELABLE_FLAG {
            Err(StatusCode::UNEXPECTED_NULL)
        } else {
            let mut parcelable = ParcelableHolder::new(Default::default());
            parcelable.read_from_parcel(parcel)?;
            Ok(parcelable)
        }
    }
}

impl Parcelable for ParcelableHolder {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
        parcel.write(&self.stability)?;

        let mut data = self.data.lock().unwrap();
        match *data {
            ParcelableHolderData::Empty => parcel.write(&0i32),
            ParcelableHolderData::Parcelable { ref parcelable, ref name } => {
                let length_start = parcel.get_data_position();
                parcel.write(&0i32)?;

                let data_start = parcel.get_data_position();
                parcel.write(name)?;
                parcelable.write_to_parcel(parcel)?;

                let end = parcel.get_data_position();
                // Safety: we got the position from `get_data_position`.
                unsafe {
                    parcel.set_data_position(length_start)?;
                }

                assert!(end >= data_start);
                parcel.write(&(end - data_start))?;
                // Safety: we got the position from `get_data_position`.
                unsafe {
                    parcel.set_data_position(end)?;
                }

                Ok(())
            }
            ParcelableHolderData::Parcel(ref mut p) => {
                parcel.write(&p.get_data_size())?;
                parcel.append_all_from(&*p)
            }
        }
    }

    fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<(), StatusCode> {
        if self.stability != parcel.read()? {
            return Err(StatusCode::BAD_VALUE);
        }

        let data_size: i32 = parcel.read()?;
        if data_size < 0 {
            // C++ returns BAD_VALUE here,
            // while Java returns ILLEGAL_ARGUMENT
            return Err(StatusCode::BAD_VALUE);
        }
        if data_size == 0 {
            *self.data.get_mut().unwrap() = ParcelableHolderData::Empty;
            return Ok(());
        }

        // TODO: C++ ParcelableHolder accepts sizes up to SIZE_MAX here, but we
        // only go up to i32::MAX because that's what our API uses everywhere
        let data_start = parcel.get_data_position();
        let data_end = data_start.checked_add(data_size).ok_or(StatusCode::BAD_VALUE)?;

        let mut new_parcel = Parcel::new();
        new_parcel.append_from(parcel, data_start, data_size)?;
        *self.data.get_mut().unwrap() = ParcelableHolderData::Parcel(new_parcel);

        // Safety: `append_from` checks if `data_size` overflows
        // `parcel` and returns `BAD_VALUE` if that happens. We also
        // explicitly check for negative and zero `data_size` above,
        // so `data_end` is guaranteed to be greater than `data_start`.
        unsafe {
            parcel.set_data_position(data_end)?;
        }

        Ok(())
    }
}
