// Copyright 2024 International Digital Economy Academy
//
// 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.

///|
pub(open) trait Sized {
  fn size_of(Self) -> UInt
}

///|
pub fn[T : Sized] size_of(t : T) -> UInt {
  t.size_of()
}

///|
fn size_of_varint(v : UInt64) -> UInt {
  if v < 0x80UL {
    1
  } else if v < 0x4000UL {
    2
  } else if v < 0x200000UL {
    3
  } else if v < 0x10000000UL {
    4
  } else if v < 0x800000000UL {
    5
  } else if v < 0x40000000000UL {
    6
  } else if v < 0x2000000000000UL {
    7
  } else if v < 0x100000000000000UL {
    8
  } else if v < 0x8000000000000000UL {
    9
  } else {
    10
  }
}

///|
pub impl Sized for UInt with fn size_of(self) {
  size_of_varint(self.to_uint64())
}

///|
pub impl Sized for UInt64 with fn size_of(self) {
  size_of_varint(self)
}

///|
pub impl Sized for Int with fn size_of(self) {
  size_of_varint(self.to_int64().reinterpret_as_uint64())
}

///|
pub impl Sized for Int64 with fn size_of(self) {
  size_of_varint(self.reinterpret_as_uint64())
}

///|
pub impl Sized for SInt with fn size_of(self) {
  let v64 = self.0.to_int64()
  size_of_varint(((v64 << 1) ^ (v64 >> 31)).reinterpret_as_uint64())
}

///|
pub impl Sized for SInt64 with fn size_of(self) {
  size_of_varint(((self.0 << 1) ^ (self.0 >> 63)).reinterpret_as_uint64())
}

///|
pub impl Sized for Bool with fn size_of(__) {
  1
}

///|
pub impl Sized for Enum with fn size_of(self) {
  size_of_varint(self.0.to_int64().reinterpret_as_uint64())
}

///|
pub impl Sized for Float with fn size_of(__) {
  4
}

///|
pub impl Sized for Double with fn size_of(__) {
  8
}

///|
pub impl Sized for Bytes with fn size_of(self) {
  self.length().reinterpret_as_uint()
}

///|
pub impl Sized for String with fn size_of(self) {
  self
  .iter()
  .map(ch => {
    let point = ch.to_int()
    if point <= 0x007F {
      1U
    } else if point <= 0x07FF {
      2
    } else if point <= 0xFFFF {
      3
    } else {
      4
    }
  })
  .fold(init=0, UInt::add)
}