use crate::prelude::*;
use crate::value_type::{Composite, Primitive, Value, ValueDef, Variant};
use codec::{Compact, Encode};
use scale_bits::Bits;
use scale_decode::TypeResolver;
use scale_encode::error::ErrorKind;
use scale_encode::{error::Kind, EncodeAsFields, EncodeAsType, Error};
use scale_encode::{
    Composite as EncodeComposite, CompositeField, FieldIter, Variant as EncodeVariant,
};
impl<T> EncodeAsType for Value<T> {
    fn encode_as_type_to<R: TypeResolver>(
        &self,
        type_id: R::TypeId,
        types: &R,
        out: &mut Vec<u8>,
    ) -> Result<(), Error> {
        match &self.value {
            ValueDef::Composite(val) => encode_composite(val, type_id, types, out),
            ValueDef::Variant(val) => encode_variant(val, type_id, types, out),
            ValueDef::Primitive(val) => encode_primitive(val, type_id, types, out),
            ValueDef::BitSequence(val) => encode_bitsequence(val, type_id, types, out),
        }
    }
}
impl<T> EncodeAsFields for Value<T> {
    fn encode_as_fields_to<R: TypeResolver>(
        &self,
        fields: &mut dyn FieldIter<'_, R::TypeId>,
        types: &R,
        out: &mut Vec<u8>,
    ) -> Result<(), Error> {
        match &self.value {
            ValueDef::Composite(composite) => composite.encode_as_fields_to(fields, types, out),
            _ => Err(Error::custom_str("Cannot encode non-composite Value shape into fields")),
        }
    }
}
impl<T> EncodeAsFields for Composite<T> {
    fn encode_as_fields_to<R: TypeResolver>(
        &self,
        fields: &mut dyn FieldIter<'_, R::TypeId>,
        types: &R,
        out: &mut Vec<u8>,
    ) -> Result<(), Error> {
        match self {
            Composite::Named(vals) => {
                let keyvals =
                    vals.iter().map(|(key, val)| (Some(&**key), CompositeField::new(val)));
                EncodeComposite::new(keyvals).encode_composite_fields_to(fields, types, out)
            }
            Composite::Unnamed(vals) => {
                let vals = vals.iter().map(|val| (None, CompositeField::new(val)));
                EncodeComposite::new(vals).encode_composite_fields_to(fields, types, out)
            }
        }
    }
}
fn encode_composite<T, R: TypeResolver>(
    value: &Composite<T>,
    mut type_id: R::TypeId,
    types: &R,
    out: &mut Vec<u8>,
) -> Result<(), Error> {
    fn do_encode_composite<T, R: TypeResolver>(
        value: &Composite<T>,
        type_id: R::TypeId,
        types: &R,
        out: &mut Vec<u8>,
    ) -> Result<(), Error> {
        let ctx = (type_id.clone(), out);
        let visit_composite_or_tuple =
            |(type_id, out): (R::TypeId, &mut Vec<u8>)| -> Result<(), Error> {
                match value {
                    Composite::Named(vals) => {
                        let keyvals =
                            vals.iter().map(|(key, val)| (Some(&**key), CompositeField::new(val)));
                        EncodeComposite::new(keyvals)
                            .encode_composite_as_type_to(type_id, types, out)
                    }
                    Composite::Unnamed(vals) => {
                        let vals = vals.iter().map(|val| (None, CompositeField::new(val)));
                        EncodeComposite::new(vals).encode_composite_as_type_to(type_id, types, out)
                    }
                }
            };
        let visitor = scale_type_resolver::visitor::new::<
            '_,
            (R::TypeId, &mut Vec<u8>),
            R::TypeId,
            Result<(), Error>,
            _,
        >(ctx, |(type_id, out), _| {
            let mut values = value.values();
            match (values.next(), values.next()) {
                (Some(value), None) => value.encode_as_type_to(type_id, types, out),
                _ => Err(Error::new(ErrorKind::WrongShape {
                    actual: Kind::Tuple,
                    expected_id: format!("{:?}", type_id),
                })),
            }
        })
        .visit_composite(|ctx, _, _| visit_composite_or_tuple(ctx))
        .visit_tuple(|ctx, _| visit_composite_or_tuple(ctx))
        .visit_sequence(|(_, out), _path, type_id| {
            Compact(value.len() as u32).encode_to(out);
            match value {
                Composite::Named(named_vals) => {
                    for (name, val) in named_vals {
                        val.encode_as_type_to(type_id.clone(), types, out)
                            .map_err(|e| e.at_field(name.to_string()))?;
                    }
                }
                Composite::Unnamed(vals) => {
                    for (idx, val) in vals.iter().enumerate() {
                        val.encode_as_type_to(type_id.clone(), types, out)
                            .map_err(|e| e.at_idx(idx))?;
                    }
                }
            }
            Ok(())
        })
        .visit_array(|(_, out), type_id, array_len| {
            if value.len() != array_len {
                return Err(Error::new(ErrorKind::WrongLength {
                    actual_len: value.len(),
                    expected_len: array_len,
                }));
            }
            for (idx, val) in value.values().enumerate() {
                val.encode_as_type_to(type_id.clone(), types, out).map_err(|e| e.at_idx(idx))?;
            }
            Ok(())
        })
        .visit_bit_sequence(|(_, out), store, order| {
            let format = scale_bits::Format { store, order };
            encode_vals_to_bitsequence(value.values(), out, format)
        });
        match types.resolve_type(type_id, visitor) {
            Ok(Ok(())) => Ok(()),
            Ok(Err(err)) => Err(err),
            Err(err) => Err(Error::new(ErrorKind::TypeResolvingError(format!("{:?}", err)))),
        }
    }
    let original_error = {
        let mut temp_out = Vec::new();
        match do_encode_composite(value, type_id.clone(), types, &mut temp_out) {
            Ok(()) => {
                out.extend_from_slice(&temp_out);
                return Ok(());
            }
            Err(e) => e,
        }
    };
    {
        let (inner_type_id, inner_is_different) =
            find_single_entry_with_same_repr::<R>(type_id.clone(), types);
        if inner_is_different {
            let mut temp_out = Vec::new();
            if let Ok(()) = do_encode_composite(value, inner_type_id.clone(), types, &mut temp_out)
            {
                out.extend_from_slice(&temp_out);
                return Ok(());
            }
            type_id = inner_type_id;
        }
    }
    if let Some(value) = get_only_value_from_composite(value) {
        let mut temp_out = Vec::new();
        if let Ok(()) = value.encode_as_type_to(type_id.clone(), types, &mut temp_out) {
            out.extend_from_slice(&temp_out);
            return Ok(());
        }
    }
    Err(original_error)
}
fn find_single_entry_with_same_repr<R: TypeResolver>(
    type_id: R::TypeId,
    types: &R,
) -> (R::TypeId, bool) {
    fn do_find<R: TypeResolver>(
        type_id: R::TypeId,
        types: &R,
        type_id_has_changed: bool,
    ) -> (R::TypeId, bool) {
        let ctx = (type_id.clone(), type_id_has_changed);
        let visitor = scale_type_resolver::visitor::new(ctx.clone(), |ctx, _| ctx)
            .visit_tuple(|return_unchanged, fields| {
                if fields.len() == 1 {
                    let ty = fields.next().expect("has 1 item; qed;");
                    do_find(ty, types, true)
                } else {
                    return_unchanged
                }
            })
            .visit_composite(|return_unchanged, _, fields| {
                if fields.len() == 1 {
                    let ty = fields.next().expect("has 1 item; qed;").id;
                    do_find(ty, types, true)
                } else {
                    return_unchanged
                }
            })
            .visit_array(
                |return_unchanged, ty_id, len| {
                    if len == 1 {
                        do_find(ty_id, types, true)
                    } else {
                        return_unchanged
                    }
                },
            );
        types.resolve_type(type_id, visitor).unwrap_or(ctx)
    }
    do_find(type_id, types, false)
}
fn get_only_value_from_composite<T>(value: &'_ Composite<T>) -> Option<&'_ Value<T>> {
    let mut values = value.values();
    match (values.next(), values.next()) {
        (Some(value), None) => Some(value),
        _ => None,
    }
}
fn encode_vals_to_bitsequence<'a, T: 'a>(
    vals: impl ExactSizeIterator<Item = &'a Value<T>>,
    out: &mut Vec<u8>,
    format: scale_bits::Format,
) -> Result<(), Error> {
    let mut bools = Vec::with_capacity(vals.len());
    for (idx, value) in vals.enumerate() {
        if let Some(v) = value.as_bool() {
            bools.push(v);
        } else if let Some(v) = value.as_u128() {
            if v == 0 || v == 1 {
                bools.push(v == 1)
            } else {
                return Err(Error::custom_str(
                    "Cannot encode non-boolean/0/1 value into a bit sequence entry",
                )
                .at_idx(idx));
            }
        } else if let Some(v) = value.as_i128() {
            if v == 0 || v == 1 {
                bools.push(v == 1)
            } else {
                return Err(Error::custom_str(
                    "Cannot encode non-boolean/0/1 value into a bit sequence entry",
                )
                .at_idx(idx));
            }
        } else {
            return Err(Error::custom_str(
                "Cannot encode non-boolean/0/1 value into a bit sequence entry",
            )
            .at_idx(idx));
        }
    }
    scale_bits::encode_using_format_to(bools.into_iter(), format, out);
    Ok(())
}
fn encode_variant<T, R: TypeResolver>(
    value: &Variant<T>,
    type_id: R::TypeId,
    types: &R,
    out: &mut Vec<u8>,
) -> Result<(), Error> {
    match &value.values {
        Composite::Named(vals) => {
            let keyvals = vals.iter().map(|(key, val)| (Some(&**key), CompositeField::new(val)));
            EncodeVariant { name: &value.name, fields: EncodeComposite::new(keyvals) }
                .encode_variant_as_type_to(type_id, types, out)
        }
        Composite::Unnamed(vals) => {
            let vals = vals.iter().map(|val| (None, CompositeField::new(val)));
            EncodeVariant { name: &value.name, fields: EncodeComposite::new(vals) }
                .encode_variant_as_type_to(type_id, types, out)
        }
    }
}
fn encode_primitive<R: TypeResolver>(
    value: &Primitive,
    type_id: R::TypeId,
    types: &R,
    bytes: &mut Vec<u8>,
) -> Result<(), Error> {
    match value {
        Primitive::Bool(val) => val.encode_as_type_to(type_id, types, bytes),
        Primitive::Char(val) => val.encode_as_type_to(type_id, types, bytes),
        Primitive::String(val) => val.encode_as_type_to(type_id, types, bytes),
        Primitive::U128(val) => val.encode_as_type_to(type_id, types, bytes),
        Primitive::I128(val) => val.encode_as_type_to(type_id, types, bytes),
        Primitive::U256(val) => val.encode_as_type_to(type_id, types, bytes),
        Primitive::I256(val) => val.encode_as_type_to(type_id, types, bytes),
    }
}
fn encode_bitsequence<R: TypeResolver>(
    value: &Bits,
    type_id: R::TypeId,
    types: &R,
    bytes: &mut Vec<u8>,
) -> Result<(), Error> {
    value.encode_as_type_to(type_id, types, bytes)
}
#[cfg(test)]
mod test {
    use super::*;
    use crate::value;
    use codec::{Compact, Encode};
    use core::time::Duration;
    use scale_info::PortableRegistry;
    #[cfg(feature = "std")]
    fn panic_after<T, F>(d: Duration, f: F) -> T
    where
        T: Send + 'static,
        F: FnOnce() -> T,
        F: Send + 'static,
    {
        use std::{sync::mpsc, thread};
        let (done_tx, done_rx) = mpsc::channel();
        let handle = thread::spawn(move || {
            let val = f();
            done_tx.send(()).expect("Unable to send completion signal");
            val
        });
        match done_rx.recv_timeout(d) {
            Ok(_) => handle.join().expect("Thread panicked"),
            Err(_) => panic!("Thread took too long"),
        }
    }
    fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, PortableRegistry) {
        let m = scale_info::MetaType::new::<T>();
        let mut types = scale_info::Registry::new();
        let id = types.register_type(&m);
        let portable_registry: PortableRegistry = types.into();
        (id.id, portable_registry)
    }
    fn assert_can_encode_to_type<T: Encode + scale_info::TypeInfo + 'static>(
        value: Value<()>,
        ty: T,
    ) {
        let expected = ty.encode();
        let mut buf = Vec::new();
        let (ty_id, types) = make_type::<T>();
        value.encode_as_type_to(ty_id, &types, &mut buf).expect("error encoding value as type");
        assert_eq!(expected, buf);
    }
    #[test]
    fn can_encode_basic_primitive_values() {
        assert_can_encode_to_type(Value::i128(123), 123i8);
        assert_can_encode_to_type(Value::i128(123), 123i16);
        assert_can_encode_to_type(Value::i128(123), 123i32);
        assert_can_encode_to_type(Value::i128(123), 123i64);
        assert_can_encode_to_type(Value::i128(123), 123i128);
        assert_can_encode_to_type(Value::u128(123), 123u8);
        assert_can_encode_to_type(Value::u128(123), 123u16);
        assert_can_encode_to_type(Value::u128(123), 123u32);
        assert_can_encode_to_type(Value::u128(123), 123u64);
        assert_can_encode_to_type(Value::u128(123), 123u128);
        assert_can_encode_to_type(Value::bool(true), true);
        assert_can_encode_to_type(Value::bool(false), false);
        assert_can_encode_to_type(Value::string("Hello"), "Hello");
        assert_can_encode_to_type(Value::string("Hello"), "Hello".to_string());
    }
    #[test]
    fn chars_encoded_like_numbers() {
        assert_can_encode_to_type(Value::char('j'), 'j' as u32);
        assert_can_encode_to_type(Value::char('j'), b'j');
    }
    #[test]
    fn can_encode_primitive_arrs_to_array() {
        use crate::Primitive;
        assert_can_encode_to_type(Value::primitive(Primitive::U256([12u8; 32])), [12u8; 32]);
        assert_can_encode_to_type(Value::primitive(Primitive::I256([12u8; 32])), [12u8; 32]);
    }
    #[test]
    fn can_encode_primitive_arrs_to_vecs() {
        use crate::Primitive;
        assert_can_encode_to_type(Value::primitive(Primitive::U256([12u8; 32])), vec![12u8; 32]);
        assert_can_encode_to_type(Value::primitive(Primitive::I256([12u8; 32])), vec![12u8; 32]);
    }
    #[test]
    fn can_encode_arrays() {
        let value = Value::unnamed_composite(vec![
            Value::u128(1),
            Value::u128(2),
            Value::u128(3),
            Value::u128(4),
        ]);
        assert_can_encode_to_type(value, [1u16, 2, 3, 4]);
    }
    #[test]
    fn can_encode_variants() {
        #[derive(Encode, scale_info::TypeInfo)]
        enum Foo {
            Named { hello: String, foo: bool },
            Unnamed(u64, Vec<bool>),
        }
        let named_value = value!(Named { foo: true, hello: "world" });
        assert_can_encode_to_type(named_value, Foo::Named { hello: "world".into(), foo: true });
        let unnamed_value = value!(Unnamed(123u64, (true, false, true)));
        assert_can_encode_to_type(unnamed_value, Foo::Unnamed(123, vec![true, false, true]));
    }
    #[test]
    fn can_encode_vec_tuples() {
        let vec_tuple = value!(((20u8, 30u16)));
        assert_can_encode_to_type(vec_tuple, vec![(20u8, 30u16)]);
    }
    #[test]
    fn can_encode_structs() {
        #[derive(Encode, scale_info::TypeInfo)]
        struct Foo {
            hello: String,
            foo: bool,
        }
        let named_value = value!({foo: true, hello: "world"});
        assert_can_encode_to_type(named_value, Foo { hello: "world".into(), foo: true });
    }
    #[test]
    fn can_encode_tuples_from_named_composite() {
        let named_value = value!({hello: "world", foo: true});
        assert_can_encode_to_type(named_value, ("world", true));
    }
    #[test]
    fn can_encode_tuples_from_unnamed_composite() {
        let unnamed_value = value!(("world", true));
        assert_can_encode_to_type(unnamed_value, ("world", true));
    }
    #[test]
    fn can_encode_unnamed_composite_to_named_struct() {
        #[derive(Encode, scale_info::TypeInfo)]
        struct Foo {
            hello: String,
            foo: bool,
        }
        let unnamed_value = value!(("world", true));
        assert_can_encode_to_type(unnamed_value, Foo { hello: "world".to_string(), foo: true });
    }
    #[test]
    fn can_encode_bitvecs() {
        use scale_bits::bits;
        assert_can_encode_to_type(
            Value::bit_sequence(bits![0, 1, 1, 0, 0, 1]),
            bits![0, 1, 1, 0, 0, 1],
        );
        assert_can_encode_to_type(value!((false)), bits![0]);
        assert_can_encode_to_type(
            value!((false, true, true, false, false, true)),
            bits![0, 1, 1, 0, 0, 1],
        );
        assert_can_encode_to_type(value!((0u8, 1u8, 1u8, 0u8, 0u8, 1u8)), bits![0, 1, 1, 0, 0, 1]);
        assert_can_encode_to_type(value!((0, 1, 1, 0, 0, 1)), bits![0, 1, 1, 0, 0, 1]);
    }
    #[test]
    fn can_encode_to_compact_types() {
        assert_can_encode_to_type(Value::u128(123), Compact(123u64));
        assert_can_encode_to_type(Value::u128(123), Compact(123u64));
        assert_can_encode_to_type(Value::u128(123), Compact(123u64));
        assert_can_encode_to_type(Value::u128(123), Compact(123u64));
        assert_can_encode_to_type(value!((123)), Compact(123u64));
        assert_can_encode_to_type(value!(({foo: 123u64})), Compact(123u64));
    }
    #[test]
    fn can_encode_skipping_struct_newtype_wrappers() {
        #[derive(Encode, scale_info::TypeInfo)]
        struct Foo {
            inner: u32,
        }
        assert_can_encode_to_type(Value::u128(32), Foo { inner: 32 });
        #[derive(Encode, scale_info::TypeInfo)]
        struct Bar(Foo);
        assert_can_encode_to_type(Value::u128(32), Bar(Foo { inner: 32 }));
        assert_can_encode_to_type(value!((32u8)), Bar(Foo { inner: 32 }));
        #[derive(Encode, scale_info::TypeInfo)]
        struct SomeBytes(Vec<u8>);
        assert_can_encode_to_type(
            Value::from_bytes([1, 2, 3, 4, 5]),
            SomeBytes(vec![1, 2, 3, 4, 5]),
        );
        assert_can_encode_to_type(Value::from_bytes([1]), SomeBytes(vec![1]));
    }
    #[test]
    fn can_encode_skipping_value_newtype_wrappers() {
        #[derive(Encode, scale_info::TypeInfo)]
        struct Foo {
            inner: u32,
        }
        assert_can_encode_to_type(
            Value::unnamed_composite(vec![Value::u128(32)]),
            Foo { inner: 32 },
        );
        assert_can_encode_to_type(
            Value::unnamed_composite(vec![Value::unnamed_composite(vec![Value::u128(32)])]),
            Foo { inner: 32 },
        );
        assert_can_encode_to_type(
            Value::unnamed_composite(vec![Value::unnamed_composite(vec![
                Value::unnamed_composite(vec![Value::u128(32)]),
            ])]),
            Foo { inner: 32 },
        );
    }
    #[test]
    #[cfg(feature = "std")]
    fn encoding_shouldnt_take_forever() {
        panic_after(Duration::from_millis(100), || {
            #[derive(scale_info::TypeInfo, codec::Encode)]
            struct A(bool);
            let mut buf = Vec::new();
            let (ty_id, types) = make_type::<A>();
            let value = Value::unnamed_composite(vec![Value::from_bytes(0u32.to_le_bytes())]);
            value
                .encode_as_type_to(ty_id, &types, &mut buf)
                .expect_err("We tried encoding a Value of the wrong shape so this should fail");
        })
    }
}