use hidane_proto::google::firestore::v1::{Value, value::ValueType};
use crate::order::{is_vector, numeric_id, vector_elements};
const NULL: u8 = 0x05;
const BOOLEAN: u8 = 0x0A;
const NAN: u8 = 0x0F;
const NUMBER: u8 = 0x14;
const TIMESTAMP: u8 = 0x19;
const STRING: u8 = 0x1E;
const BYTES: u8 = 0x23;
const REFERENCE: u8 = 0x28;
const GEO_POINT: u8 = 0x2D;
const ARRAY: u8 = 0x32;
const VECTOR: u8 = 0x37;
const MAP: u8 = 0x3C;
const EXPRESSION: u8 = 0x41;
const SEQUENCE_END: u8 = 0x00;
const ESCAPE: [u8; 2] = [0x00, 0xFF];
const TERMINATOR: [u8; 2] = [0x00, 0x01];
const MAP_END: [u8; 2] = [0x00, 0x00];
const PATH_END: u8 = 0x00;
const PATH_NUMERIC_SEGMENT: u8 = 0x01;
const PATH_STRING_SEGMENT: u8 = 0x02;
const PATH_AFTER_DESCENDANTS: u8 = 0x03;
pub fn encode_value(value: &Value) -> Vec<u8> {
let mut out = Vec::new();
write_value(value, &mut out);
out
}
pub fn encode_escaped(bytes: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
write_escaped(bytes, &mut out);
out
}
pub fn write_value(value: &Value, out: &mut Vec<u8>) {
match &value.value_type {
None | Some(ValueType::NullValue(_)) => out.push(NULL),
Some(ValueType::BooleanValue(b)) => out.extend([BOOLEAN, u8::from(*b)]),
Some(ValueType::IntegerValue(i)) => write_integer(*i, out),
Some(ValueType::DoubleValue(d)) => write_double(*d, out),
Some(ValueType::TimestampValue(ts)) => {
out.push(TIMESTAMP);
out.extend(sortable_i64(ts.seconds));
out.extend(ts.nanos.to_be_bytes());
}
Some(ValueType::StringValue(s)) => {
out.push(STRING);
write_escaped(s.as_bytes(), out);
}
Some(ValueType::BytesValue(b)) => {
out.push(BYTES);
write_escaped(b, out);
}
Some(ValueType::ReferenceValue(name)) => {
out.push(REFERENCE);
write_path(name.split('/'), out);
}
Some(ValueType::GeoPointValue(point)) => {
out.push(GEO_POINT);
out.extend(sortable_f64(point.latitude));
out.extend(sortable_f64(point.longitude));
}
Some(ValueType::ArrayValue(array)) => {
out.push(ARRAY);
for element in &array.values {
write_value(element, out);
}
out.push(SEQUENCE_END);
}
Some(ValueType::MapValue(map)) if is_vector(map) => {
let elements = vector_elements(map);
out.push(VECTOR);
out.extend(
u32::try_from(elements.len())
.unwrap_or(u32::MAX)
.to_be_bytes(),
);
for element in elements {
write_value(element, out);
}
}
Some(ValueType::MapValue(map)) => {
out.push(MAP);
for (key, field) in &map.fields {
write_escaped(key.as_bytes(), out);
write_value(field, out);
}
out.extend(MAP_END);
}
Some(
ValueType::FieldReferenceValue(_)
| ValueType::VariableReferenceValue(_)
| ValueType::FunctionValue(_)
| ValueType::PipelineValue(_),
) => out.push(EXPRESSION),
}
}
pub fn encode_path<'a>(segments: impl IntoIterator<Item = &'a str>) -> Vec<u8> {
let mut out = Vec::new();
write_path(segments, &mut out);
out
}
pub fn write_path<'a>(segments: impl IntoIterator<Item = &'a str>, out: &mut Vec<u8>) {
write_path_prefix(segments, out);
out.push(PATH_END);
}
pub fn encode_path_prefix<'a>(segments: impl IntoIterator<Item = &'a str>) -> Vec<u8> {
let mut out = Vec::new();
write_path_prefix(segments, &mut out);
out
}
pub fn path_subtree_end(prefix: &[u8]) -> Vec<u8> {
let mut end = prefix.to_vec();
end.push(PATH_AFTER_DESCENDANTS);
end
}
fn write_path_prefix<'a>(segments: impl IntoIterator<Item = &'a str>, out: &mut Vec<u8>) {
for segment in segments {
match numeric_id(segment) {
Some(id) => {
out.push(PATH_NUMERIC_SEGMENT);
out.extend(sortable_i64(id));
}
None => {
out.push(PATH_STRING_SEGMENT);
write_escaped(segment.as_bytes(), out);
}
}
}
}
fn write_integer(i: i64, out: &mut Vec<u8>) {
#[allow(clippy::cast_precision_loss)]
let nearest = i as f64;
#[allow(clippy::cast_possible_truncation)]
let distance = i128::from(i) - nearest as i128;
out.push(NUMBER);
out.extend(sortable_f64(nearest));
out.extend(sortable_i16(
i16::try_from(distance).expect("|i - round(i)| <= 1024"),
));
}
fn write_double(d: f64, out: &mut Vec<u8>) {
if d.is_nan() {
out.push(NAN);
return;
}
out.push(NUMBER);
out.extend(sortable_f64(d));
out.extend(sortable_i16(0));
}
fn write_escaped(bytes: &[u8], out: &mut Vec<u8>) {
for &b in bytes {
if b == 0x00 {
out.extend(ESCAPE);
} else {
out.push(b);
}
}
out.extend(TERMINATOR);
}
fn sortable_i64(v: i64) -> [u8; 8] {
(v.cast_unsigned() ^ (1 << 63)).to_be_bytes()
}
fn sortable_i16(v: i16) -> [u8; 2] {
(v.cast_unsigned() ^ (1 << 15)).to_be_bytes()
}
fn sortable_f64(v: f64) -> [u8; 8] {
let v = if v == 0.0 { 0.0 } else { v };
let bits = v.to_bits();
let bits = if bits >> 63 == 1 {
!bits
} else {
bits ^ (1 << 63)
};
bits.to_be_bytes()
}