Documentation
use arrayref::{array_refs, mut_array_refs};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    entrypoint::ProgramResult,
    program_error::ProgramError,
    program_pack::{IsInitialized, Pack, Sealed},
    pubkey::Pubkey,
};
use std::{str::from_utf8, str::Utf8Error};

#[derive(BorshSerialize, BorshDeserialize, Debug, Default, PartialEq)]
pub struct Bet {
    pub question: Pubkey,
    pub yesno: u8,
    pub initialized: bool,
}

impl Sealed for Bet {}

impl IsInitialized for Bet {
    fn is_initialized(&self) -> bool {
        self.initialized
    }
}

impl Pack for Bet {
    const LEN: usize = 323;
    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
        match Bet::try_from_slice(src) {
            Ok(q) => Ok(q),
            Err(_) => Err(ProgramError::InvalidAccountData),
        }
    }

    fn pack_into_slice(&self, dst: &mut [u8]) {
        let vec = self.try_to_vec().unwrap();
        dst.copy_from_slice(&vec);
    }
}

impl Bet {
    pub fn setup(&mut self, question: &Pubkey, yesno: u8) -> ProgramResult {
        if self.is_initialized() {
            return Err(ProgramError::AccountAlreadyInitialized);
        }
        if yesno != 1 || yesno != 2 {
            return Err(ProgramError::InvalidAccountData);
        }

        self.question = *question;
        self.yesno = yesno;
        Ok(())
    }
}

#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq)]
pub struct YesNoQuestion {
    pub title: [u8; 256],
    pub deadline: u64,
    pub closed: bool,
    pub ans: u8,
    pub owner: Pubkey,
    pub yes_count: u64,
    pub no_count: u64,
    pub initialized: bool,
    pub pool: u64,
}

impl Default for YesNoQuestion {
    fn default() -> Self {
        YesNoQuestion {
            title: [0; 256],
            deadline: 0,
            closed: false,
            ans: 0,
            owner: Pubkey::default(),
            yes_count: 0,
            no_count: 0,
            initialized: true,
            pool: 0,
        }
    }
}

impl Sealed for YesNoQuestion {}

impl IsInitialized for YesNoQuestion {
    fn is_initialized(&self) -> bool {
        self.initialized
    }
}

impl Pack for YesNoQuestion {
    const LEN: usize = 323;
    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
        match YesNoQuestion::try_from_slice(src) {
            Ok(q) => Ok(q),
            Err(_) => Err(ProgramError::InvalidAccountData),
        }
    }

    fn pack_into_slice(&self, dst: &mut [u8]) {
        let vec = self.try_to_vec().unwrap();
        dst.copy_from_slice(&vec);
    }
}

impl YesNoQuestion {
    pub fn unpack_check_duplicated(src: &[u8]) -> Result<Self, ProgramError> {
        match YesNoQuestion::try_from_slice(src) {
            Ok(q) => {
                if q.is_initialized() {
                    return Err(ProgramError::AccountAlreadyInitialized);
                }
                return Ok(q);
            }
            Err(_) => Err(ProgramError::InvalidAccountData),
        }
    }

    pub fn setup(
        &mut self,
        title: &str,
        deadline: u64,
        pool: u64,
        owner: &Pubkey,
    ) -> ProgramResult {
        if self.initialized {
            return Err(ProgramError::AccountAlreadyInitialized);
        }
        if pack_str(title, &mut self.title).is_err() {
            return Err(ProgramError::InvalidAccountData);
        }

        self.deadline = deadline;
        self.pool = pool;
        self.owner = *owner;
        self.initialized = true;
        Ok(())
    }

    pub fn add(&mut self, yesno: u8) -> ProgramResult {
        if yesno == 1 {
            self.yes_count += 1;
        } else if yesno == 2 {
            self.no_count += 1;
        } else {
            return Err(ProgramError::InvalidAccountData);
        }
        self.pool += 1;
        Ok(())
    }
}

pub fn unpack_str<'a>(src: &'a [u8; 256]) -> Result<&'a str, Utf8Error> {
    let (str_len, rest) = array_refs![src, 2, 254];
    let str_len = u16::from_le_bytes(*str_len);
    let (str, _) = rest.split_at(str_len as usize);
    match from_utf8(str) {
        Ok(res) => Ok(res),
        Err(e) => Err(e),
    }
}

pub fn pack_str(str: &str, to: &mut [u8; 256]) -> Result<(), String> {
    let (str_len, rest) = mut_array_refs![to, 2, 254];
    if str.len() > 254 {
        return Err("string len should be less and equal than 245".to_string());
    }
    let str_len_bytes = str.len().to_le_bytes();
    str_len[0] = str_len_bytes[0];
    str_len[1] = str_len_bytes[1];
    let str_bytes = str.as_bytes();
    for (i, char) in str_bytes.iter().enumerate() {
        rest[i] = *char;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use crate::state::*;

    #[test]
    fn test_str_pack_unpack() {
        let str_test = "你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你";
        let mut str_bytes: [u8; 256] = [0; 256];
        pack_str(str_test, &mut str_bytes).unwrap();

        let unpack_str = unpack_str(&str_bytes).unwrap();
        assert_eq!(str_test, unpack_str);
    }

    #[test]
    fn test_yesno_question() {
        let q = YesNoQuestion::default();
    }
}