use amplify::confinement;
use amplify::confinement::Confined;
use crate::opcodes::*;
use crate::{ScriptHash, VarInt, VarIntArray, VarIntBytes, LIB_NAME_BITCOIN};
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
#[wrapper(Deref, AsSlice, Hex)]
#[wrapper_mut(DerefMut, AsSliceMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", transparent)
)]
pub struct SigScript(ScriptBytes);
impl TryFrom<Vec<u8>> for SigScript {
    type Error = confinement::Error;
    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
        ScriptBytes::try_from(script_bytes).map(Self)
    }
}
impl SigScript {
    #[inline]
    pub fn empty() -> Self { SigScript::default() }
    #[inline]
    pub fn new() -> Self { Self::default() }
    #[inline]
    pub fn with_capacity(capacity: usize) -> Self {
        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
    }
    #[inline]
    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
        Self(ScriptBytes::from_unsafe(script_bytes))
    }
    #[inline]
    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
#[wrapper(Deref, AsSlice, Hex)]
#[wrapper_mut(DerefMut, AsSliceMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", transparent)
)]
pub struct ScriptPubkey(ScriptBytes);
impl TryFrom<Vec<u8>> for ScriptPubkey {
    type Error = confinement::Error;
    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
        ScriptBytes::try_from(script_bytes).map(Self)
    }
}
impl ScriptPubkey {
    #[inline]
    pub fn new() -> Self { Self::default() }
    #[inline]
    pub fn with_capacity(capacity: usize) -> Self {
        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
    }
    #[inline]
    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
        Self(ScriptBytes::from_unsafe(script_bytes))
    }
    pub fn p2pkh(hash: impl Into<[u8; 20]>) -> Self {
        let mut script = Self::with_capacity(25);
        script.push_opcode(OpCode::Dup);
        script.push_opcode(OpCode::Hash160);
        script.push_slice(&hash.into());
        script.push_opcode(OpCode::EqualVerify);
        script.push_opcode(OpCode::CheckSig);
        script
    }
    pub fn p2sh(hash: impl Into<[u8; 20]>) -> Self {
        let mut script = Self::with_capacity(23);
        script.push_opcode(OpCode::Hash160);
        script.push_slice(&hash.into());
        script.push_opcode(OpCode::Equal);
        script
    }
    pub fn op_return(data: &[u8]) -> Self {
        let mut script = Self::with_capacity(ScriptBytes::len_for_slice(data.len()) + 1);
        script.push_opcode(OpCode::Return);
        script.push_slice(data);
        script
    }
    #[inline]
    pub fn is_p2pkh(&self) -> bool {
        self.0.len() == 25 &&
            self.0[0] == OP_DUP &&
            self.0[1] == OP_HASH160 &&
            self.0[2] == OP_PUSHBYTES_20 &&
            self.0[23] == OP_EQUALVERIFY &&
            self.0[24] == OP_CHECKSIG
    }
    #[inline]
    pub fn is_p2sh(&self) -> bool {
        self.0.len() == 23 &&
            self.0[0] == OP_HASH160 &&
            self.0[1] == OP_PUSHBYTES_20 &&
            self.0[22] == OP_EQUAL
    }
    #[inline]
    pub fn is_op_return(&self) -> bool { !self.is_empty() && self[0] == OpCode::Return as u8 }
    #[inline]
    pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8) }
    #[inline]
    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
#[wrapper(Deref, AsSlice, Hex)]
#[wrapper_mut(DerefMut, AsSliceMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", transparent)
)]
pub struct RedeemScript(ScriptBytes);
impl TryFrom<Vec<u8>> for RedeemScript {
    type Error = confinement::Error;
    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
        ScriptBytes::try_from(script_bytes).map(Self)
    }
}
impl RedeemScript {
    #[inline]
    pub fn new() -> Self { Self::default() }
    #[inline]
    pub fn with_capacity(capacity: usize) -> Self {
        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
    }
    #[inline]
    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
        Self(ScriptBytes::from_unsafe(script_bytes))
    }
    #[inline]
    pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); }
    pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2sh(ScriptHash::from(self)) }
    #[inline]
    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)]
#[wrapper(Deref, AsSlice, Hex)]
#[wrapper_mut(DerefMut, AsSliceMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
pub struct ScriptBytes(VarIntBytes);
impl TryFrom<Vec<u8>> for ScriptBytes {
    type Error = confinement::Error;
    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
        Confined::try_from(script_bytes).map(Self)
    }
}
impl ScriptBytes {
    #[inline]
    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
        Self(Confined::try_from(script_bytes).expect("script exceeding 4GB"))
    }
    pub fn push_slice(&mut self, data: &[u8]) {
        match data.len() as u64 {
            n if n < OP_PUSHDATA1 as u64 => {
                self.push(n as u8);
            }
            n if n < 0x100 => {
                self.push(OP_PUSHDATA1);
                self.push(n as u8);
            }
            n if n < 0x10000 => {
                self.push(OP_PUSHDATA2);
                self.push((n % 0x100) as u8);
                self.push((n / 0x100) as u8);
            }
            n if n < 0x100000000 => {
                self.push(OP_PUSHDATA4);
                self.push((n % 0x100) as u8);
                self.push(((n / 0x100) % 0x100) as u8);
                self.push(((n / 0x10000) % 0x100) as u8);
                self.push((n / 0x1000000) as u8);
            }
            _ => panic!("tried to put a 4bn+ sized object into a script!"),
        }
        self.extend(data);
    }
    #[inline]
    pub(crate) fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") }
    #[inline]
    pub(crate) fn extend(&mut self, data: &[u8]) {
        self.0
            .extend(data.iter().copied())
            .expect("script exceeds 4GB")
    }
    pub fn len_for_slice(len: usize) -> usize {
        len + match len {
            0..=0x4b => 1,
            0x4c..=0xff => 2,
            0x100..=0xffff => 3,
            _ => 5,
        }
    }
    pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) }
    pub fn into_vec(self) -> Vec<u8> { self.0.into_inner() }
    pub(crate) fn as_var_int_array(&self) -> &VarIntArray<u8> { &self.0 }
}
#[cfg(feature = "serde")]
mod _serde {
    use amplify::hex::{FromHex, ToHex};
    use serde::{Deserialize, Serialize};
    use serde_crate::de::Error;
    use serde_crate::{Deserializer, Serializer};
    use super::*;
    impl Serialize for ScriptBytes {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer {
            if serializer.is_human_readable() {
                serializer.serialize_str(&self.to_hex())
            } else {
                serializer.serialize_bytes(self.as_slice())
            }
        }
    }
    impl<'de> Deserialize<'de> for ScriptBytes {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de> {
            if deserializer.is_human_readable() {
                String::deserialize(deserializer).and_then(|string| {
                    Self::from_hex(&string).map_err(|_| D::Error::custom("wrong hex data"))
                })
            } else {
                let bytes = Vec::<u8>::deserialize(deserializer)?;
                Self::try_from(bytes)
                    .map_err(|_| D::Error::custom("invalid script length exceeding 4GB"))
            }
        }
    }
}
#[cfg(test)]
mod test {
    use amplify::hex::ToHex;
    use super::*;
    #[test]
    fn script_index() {
        let mut script = ScriptPubkey::op_return(&[0u8; 40]);
        assert_eq!(script[0], OP_RETURN);
        assert_eq!(&script[..2], &[OP_RETURN, OP_PUSHBYTES_40]);
        assert_eq!(&script[40..], &[0u8, 0u8]);
        assert_eq!(&script[2..4], &[0u8, 0u8]);
        assert_eq!(&script[2..=3], &[0u8, 0u8]);
        script[0] = 0xFF;
        script[..2].copy_from_slice(&[0xFF, 0xFF]);
        script[40..].copy_from_slice(&[0xFF, 0xFF]);
        script[2..4].copy_from_slice(&[0xFF, 0xFF]);
        script[2..=3].copy_from_slice(&[0xFF, 0xFF]);
        assert_eq!(script[0], 0xFF);
        assert_eq!(&script[..2], &[0xFF, 0xFF]);
        assert_eq!(&script[40..], &[0xFF, 0xFF]);
        assert_eq!(&script[2..4], &[0xFF, 0xFF]);
        assert_eq!(&script[2..=3], &[0xFF, 0xFF]);
        assert_eq!(
            &script.to_hex(),
            "ffffffff000000000000000000000000000000000000000000000000000000000000000000000000ffff"
        );
    }
}