//! jsonrpc request

use super::{Id, Params, Version};

/// Represents jsonrpc request which is a method call.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MethodCall {
	/// A String specifying the version of the JSON-RPC protocol.
	pub jsonrpc: Option<Version>,
	/// A String containing the name of the method to be invoked.
	pub method: String,
	/// A Structured value that holds the parameter values to be used
	/// during the invocation of the method. This member MAY be omitted.
	#[serde(default = "default_params")]
	pub params: Params,
	/// An identifier established by the Client that MUST contain a String,
	/// Number, or NULL value if included. If it is not included it is assumed
	/// to be a notification.
	pub id: Id,
}

/// Represents jsonrpc request which is a notification.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Notification {
	/// A String specifying the version of the JSON-RPC protocol.
	pub jsonrpc: Option<Version>,
	/// A String containing the name of the method to be invoked.
	pub method: String,
	/// A Structured value that holds the parameter values to be used
	/// during the invocation of the method. This member MAY be omitted.
	#[serde(default = "default_params")]
	pub params: Params,
}

/// Represents single jsonrpc call.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Call {
	/// Call method
	MethodCall(MethodCall),
	/// Fire notification
	Notification(Notification),
	/// Invalid call
	Invalid {
		/// Call id (if known)
		#[serde(default = "default_id")]
		id: Id,
	},
}

fn default_params() -> Params {
	Params::None
}

fn default_id() -> Id {
	Id::Null
}

impl From<MethodCall> for Call {
	fn from(mc: MethodCall) -> Self {
		Call::MethodCall(mc)
	}
}

impl From<Notification> for Call {
	fn from(n: Notification) -> Self {
		Call::Notification(n)
	}
}

/// Represents jsonrpc request.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
pub enum Request {
	/// Single request (call)
	Single(Call),
	/// Batch of requests (calls)
	Batch(Vec<Call>),
}

#[cfg(test)]
mod tests {
	use super::*;
	use serde_json::Value;

	#[test]
	fn method_call_serialize() {
		use serde_json;
		use serde_json::Value;

		let m = MethodCall {
			jsonrpc: Some(Version::V2),
			method: "update".to_owned(),
			params: Params::Array(vec![Value::from(1), Value::from(2)]),
			id: Id::Num(1),
		};

		let serialized = serde_json::to_string(&m).unwrap();
		assert_eq!(
			serialized,
			r#"{"jsonrpc":"2.0","method":"update","params":[1,2],"id":1}"#
		);
	}

	#[test]
	fn notification_serialize() {
		use serde_json;
		use serde_json::Value;

		let n = Notification {
			jsonrpc: Some(Version::V2),
			method: "update".to_owned(),
			params: Params::Array(vec![Value::from(1), Value::from(2)]),
		};

		let serialized = serde_json::to_string(&n).unwrap();
		assert_eq!(serialized, r#"{"jsonrpc":"2.0","method":"update","params":[1,2]}"#);
	}

	#[test]
	fn call_serialize() {
		use serde_json;
		use serde_json::Value;

		let n = Call::Notification(Notification {
			jsonrpc: Some(Version::V2),
			method: "update".to_owned(),
			params: Params::Array(vec![Value::from(1)]),
		});

		let serialized = serde_json::to_string(&n).unwrap();
		assert_eq!(serialized, r#"{"jsonrpc":"2.0","method":"update","params":[1]}"#);
	}

	#[test]
	fn request_serialize_batch() {
		use serde_json;

		let batch = Request::Batch(vec![
			Call::MethodCall(MethodCall {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::Array(vec![Value::from(1), Value::from(2)]),
				id: Id::Num(1),
			}),
			Call::Notification(Notification {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::Array(vec![Value::from(1)]),
			}),
		]);

		let serialized = serde_json::to_string(&batch).unwrap();
		assert_eq!(
			serialized,
			r#"[{"jsonrpc":"2.0","method":"update","params":[1,2],"id":1},{"jsonrpc":"2.0","method":"update","params":[1]}]"#
		);
	}

	#[test]
	fn notification_deserialize() {
		use serde_json;
		use serde_json::Value;

		let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1,2]}"#;
		let deserialized: Notification = serde_json::from_str(s).unwrap();

		assert_eq!(
			deserialized,
			Notification {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::Array(vec![Value::from(1), Value::from(2)])
			}
		);

		let s = r#"{"jsonrpc": "2.0", "method": "foobar"}"#;
		let deserialized: Notification = serde_json::from_str(s).unwrap();

		assert_eq!(
			deserialized,
			Notification {
				jsonrpc: Some(Version::V2),
				method: "foobar".to_owned(),
				params: Params::None,
			}
		);

		let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1,2], "id": 1}"#;
		let deserialized: Result<Notification, _> = serde_json::from_str(s);
		assert!(deserialized.is_err());
	}

	#[test]
	fn call_deserialize() {
		use serde_json;

		let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1]}"#;
		let deserialized: Call = serde_json::from_str(s).unwrap();
		assert_eq!(
			deserialized,
			Call::Notification(Notification {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::Array(vec![Value::from(1)])
			})
		);

		let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1], "id": 1}"#;
		let deserialized: Call = serde_json::from_str(s).unwrap();
		assert_eq!(
			deserialized,
			Call::MethodCall(MethodCall {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::Array(vec![Value::from(1)]),
				id: Id::Num(1)
			})
		);

		let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [], "id": 1}"#;
		let deserialized: Call = serde_json::from_str(s).unwrap();
		assert_eq!(
			deserialized,
			Call::MethodCall(MethodCall {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::Array(vec![]),
				id: Id::Num(1)
			})
		);

		let s = r#"{"jsonrpc": "2.0", "method": "update", "params": null, "id": 1}"#;
		let deserialized: Call = serde_json::from_str(s).unwrap();
		assert_eq!(
			deserialized,
			Call::MethodCall(MethodCall {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::None,
				id: Id::Num(1)
			})
		);

		let s = r#"{"jsonrpc": "2.0", "method": "update", "id": 1}"#;
		let deserialized: Call = serde_json::from_str(s).unwrap();
		assert_eq!(
			deserialized,
			Call::MethodCall(MethodCall {
				jsonrpc: Some(Version::V2),
				method: "update".to_owned(),
				params: Params::None,
				id: Id::Num(1)
			})
		);
	}

	#[test]
	fn request_deserialize_batch() {
		use serde_json;

		let s = r#"[{}, {"jsonrpc": "2.0", "method": "update", "params": [1,2], "id": 1},{"jsonrpc": "2.0", "method": "update", "params": [1]}]"#;
		let deserialized: Request = serde_json::from_str(s).unwrap();
		assert_eq!(
			deserialized,
			Request::Batch(vec![
				Call::Invalid { id: Id::Null },
				Call::MethodCall(MethodCall {
					jsonrpc: Some(Version::V2),
					method: "update".to_owned(),
					params: Params::Array(vec![Value::from(1), Value::from(2)]),
					id: Id::Num(1)
				}),
				Call::Notification(Notification {
					jsonrpc: Some(Version::V2),
					method: "update".to_owned(),
					params: Params::Array(vec![Value::from(1)])
				})
			])
		)
	}

	#[test]
	fn request_invalid_returns_id() {
		use serde_json;

		let s = r#"{"id":120,"method":"my_method","params":["foo", "bar"],"extra_field":[]}"#;
		let deserialized: Request = serde_json::from_str(s).unwrap();

		match deserialized {
			Request::Single(Call::Invalid { id: Id::Num(120) }) => {}
			_ => panic!("Request wrongly deserialized: {:?}", deserialized),
		}
	}
}
