#![cfg(unix)]

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{
    cell::{RefCell, UnsafeCell},
    collections::HashMap,
    ffi::CStr,
};

use custom_mutator::{afl_state, export_mutator, CustomMutator};

use libafl::{
    bolts::{rands::StdRand, serdeany::SerdeAnyMap, tuples::Merge},
    corpus::{Corpus, Testcase},
    inputs::{BytesInput, HasBytesVec},
    mutators::{
        scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator, Tokens},
        Mutator,
    },
    prelude::UsesInput,
    state::{HasCorpus, HasMaxSize, HasMetadata, HasRand, State, UsesState},
    Error,
};

#[allow(clippy::identity_op)]
const MAX_FILE: usize = 1 * 1024 * 1024;

static mut AFL: Option<&'static afl_state> = None;
static mut CURRENT_ENTRY: Option<usize> = None;

fn afl() -> &'static afl_state {
    unsafe { AFL.unwrap() }
}

#[derive(Default, Debug)]
pub struct AFLCorpus {
    entries: UnsafeCell<HashMap<usize, RefCell<Testcase<BytesInput>>>>,
}

impl Clone for AFLCorpus {
    fn clone(&self) -> Self {
        unsafe {
            Self {
                entries: UnsafeCell::new(self.entries.get().as_ref().unwrap().clone()),
            }
        }
    }
}

impl Serialize for AFLCorpus {
    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        unimplemented!();
    }
}

impl<'de> Deserialize<'de> for AFLCorpus {
    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        unimplemented!();
    }
}

impl UsesState for AFLCorpus {
    type State = AFLState;
}

impl Corpus for AFLCorpus {
    #[inline]
    fn count(&self) -> usize {
        afl().queued_items as usize
    }

    #[inline]
    fn add(&mut self, _testcase: Testcase<BytesInput>) -> Result<usize, Error> {
        unimplemented!();
    }

    #[inline]
    fn replace(
        &mut self,
        _idx: usize,
        _testcase: Testcase<BytesInput>,
    ) -> Result<Testcase<Self::Input>, Error> {
        unimplemented!();
    }

    #[inline]
    fn remove(&mut self, _idx: usize) -> Result<Option<Testcase<BytesInput>>, Error> {
        unimplemented!();
    }

    #[inline]
    fn get(&self, idx: usize) -> Result<&RefCell<Testcase<BytesInput>>, Error> {
        unsafe {
            let entries = self.entries.get().as_mut().unwrap();
            entries.entry(idx).or_insert_with(|| {
                let queue_buf = std::slice::from_raw_parts_mut(afl().queue_buf, self.count());
                let entry = queue_buf[idx].as_mut().unwrap();
                let fname = CStr::from_ptr((entry.fname.cast::<i8>()).as_ref().unwrap())
                    .to_str()
                    .unwrap()
                    .to_owned();
                let mut testcase = Testcase::with_filename(BytesInput::new(vec![]), fname);
                *testcase.input_mut() = None;
                RefCell::new(testcase)
            });
            Ok(&self.entries.get().as_ref().unwrap()[&idx])
        }
    }

    #[inline]
    fn current(&self) -> &Option<usize> {
        unsafe {
            CURRENT_ENTRY = Some(afl().current_entry as usize);
            &CURRENT_ENTRY
        }
    }

    #[inline]
    fn current_mut(&mut self) -> &mut Option<usize> {
        unimplemented!();
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AFLState {
    rand: StdRand,
    corpus: AFLCorpus,
    metadata: SerdeAnyMap,
    max_size: usize,
}

impl AFLState {
    #[must_use]
    pub fn new(seed: u32) -> Self {
        Self {
            rand: StdRand::with_seed(u64::from(seed)),
            corpus: AFLCorpus::default(),
            metadata: SerdeAnyMap::new(),
            max_size: MAX_FILE,
        }
    }
}

impl State for AFLState {}

impl HasRand for AFLState {
    type Rand = StdRand;

    #[inline]
    fn rand(&self) -> &Self::Rand {
        &self.rand
    }

    #[inline]
    fn rand_mut(&mut self) -> &mut Self::Rand {
        &mut self.rand
    }
}

impl UsesInput for AFLState {
    type Input = BytesInput;
}

impl HasCorpus for AFLState {
    type Corpus = AFLCorpus;

    #[inline]
    fn corpus(&self) -> &Self::Corpus {
        &self.corpus
    }

    #[inline]
    fn corpus_mut(&mut self) -> &mut Self::Corpus {
        &mut self.corpus
    }
}

impl HasMetadata for AFLState {
    #[inline]
    fn metadata(&self) -> &SerdeAnyMap {
        &self.metadata
    }

    #[inline]
    fn metadata_mut(&mut self) -> &mut SerdeAnyMap {
        &mut self.metadata
    }
}

impl HasMaxSize for AFLState {
    fn max_size(&self) -> usize {
        self.max_size
    }

    fn set_max_size(&mut self, max_size: usize) {
        self.max_size = max_size;
    }
}

struct LibAFLBaseCustomMutator {
    state: AFLState,
    input: BytesInput,
}

impl CustomMutator for LibAFLBaseCustomMutator {
    type Error = libafl::Error;

    fn init(afl: &'static afl_state, seed: u32) -> Result<Self, Self::Error> {
        unsafe {
            AFL = Some(afl);
            let mut state = AFLState::new(seed);
            let extras = std::slice::from_raw_parts(afl.extras, afl.extras_cnt as usize);
            let mut tokens = vec![];
            for extra in extras {
                let data = std::slice::from_raw_parts(extra.data, extra.len as usize);
                tokens.push(data.to_vec());
            }
            if !tokens.is_empty() {
                state.add_metadata(Tokens::from(tokens));
            }
            Ok(Self {
                state,
                input: BytesInput::new(vec![]),
            })
        }
    }

    fn fuzz<'b, 's: 'b>(
        &'s mut self,
        buffer: &'b mut [u8],
        _add_buff: Option<&[u8]>,
        max_size: usize,
    ) -> Result<Option<&'b [u8]>, Self::Error> {
        self.state.set_max_size(max_size);

        // TODO avoid copy
        self.input.bytes_mut().clear();
        self.input.bytes_mut().extend_from_slice(buffer);

        let mut mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
        mutator.mutate(&mut self.state, &mut self.input, 0)?;
        Ok(Some(self.input.bytes()))
    }
}

export_mutator!(LibAFLBaseCustomMutator);
