use core::mem;
use std;
use core::marker::PhantomData;
use std::thread::{
    JoinHandle,
};

use brotli::dictionary::{kBrotliDictionary, kBrotliDictionarySizeBitsByLength,
                         kBrotliDictionaryOffsetsByLength};
use brotli::transform::{TransformDictionaryWord};
use brotli::interface;
use std::collections::BTreeMap;
use std::fmt;
use alloc_no_stdlib::{SliceWrapper, Allocator};
use brotli::enc::BrotliAlloc;
use brotli::enc::threading::{
  SendAlloc,
  InternalSendAlloc,
  BatchSpawnable,
  BatchSpawnableLite,
  Joinable,
  Owned,
  OwnedRetriever,
  InternalOwned,
  BrotliEncoderThreadError,
  AnyBoxConstructor,
  PoisonedThreadError,
};

struct HexSlice<'a>(&'a [u8]);

impl<'a> fmt::Display for HexSlice<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for byte in self.0 {
            if let Err(e) = write!(f, "{:02X}", byte) {
                return Err(e);
            }
        }
        Ok(())
    }
}
pub fn permute_dictionary() -> BTreeMap<Vec<u8>, ()> {
    let mut ret = BTreeMap::<Vec<u8>, ()>::new();
    let mut transformed = [0u8;38];
    for wordlen in 4..kBrotliDictionaryOffsetsByLength.len() {
        let offset = kBrotliDictionaryOffsetsByLength[wordlen] as usize;
        for index in 0..(1 << kBrotliDictionarySizeBitsByLength[wordlen]) {
            let word = &kBrotliDictionary[offset + index..offset + index + wordlen];
            for transform in 0..121 {
                let final_size = TransformDictionaryWord(&mut transformed[..],
                                        word,
                                        wordlen as i32,
                                        transform as i32) as usize;
                let vec : Vec<u8> = transformed[..final_size].to_vec();
                ret.insert(vec, ());
            }
        }
    }
    ret
}

pub fn print_dictionary(dict :BTreeMap<Vec<u8>, ()>) {
    for (key, _) in dict {
        println!("{}", HexSlice(&key[..]));
    }
}
macro_rules! println_stderr(
    ($($val:tt)*) => { {
        writeln!(&mut ::std::io::stderr(), $($val)*).unwrap();
    } }
);

fn prediction_mode_str(prediction_mode_nibble:interface::LiteralPredictionModeNibble) -> &'static str {
   match prediction_mode_nibble.prediction_mode() {
         interface::LITERAL_PREDICTION_MODE_SIGN => "sign",
         interface::LITERAL_PREDICTION_MODE_LSB6 => "lsb6",
         interface::LITERAL_PREDICTION_MODE_MSB6 => "msb6",
         interface::LITERAL_PREDICTION_MODE_UTF8 => "utf8",
         _ => "unknown",
   }
}

struct SliceU8Ref<'a>(pub &'a[u8]);

impl<'a> fmt::LowerHex for SliceU8Ref<'a> {
    fn fmt(&self, fmtr: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        for item in self.0 {
            if let Err(e) = fmtr.write_fmt(format_args!("{:02x}", item)) {
                return Err(e);
            }
        }
        Ok(())
    }
}

pub fn write_one<T:SliceWrapper<u8>>(cmd: &interface::Command<T>) {
    use std::io::Write;
    match cmd {
        &interface::Command::BlockSwitchLiteral(ref bsl) => {
            println_stderr!("ltype {} {}", bsl.0.block_type(), bsl.1);
        },
        &interface::Command::BlockSwitchCommand(ref bsc) => {
            println_stderr!("ctype {}", bsc.0);
        },
        &interface::Command::BlockSwitchDistance(ref bsd) => {
            println_stderr!("dtype {}", bsd.0);
        },
        &interface::Command::PredictionMode(ref prediction) => {
            let prediction_mode = prediction_mode_str(prediction.literal_prediction_mode());
            let lit_cm = prediction.literal_context_map.slice().iter().fold(::std::string::String::new(),
                                                                            |res, &val| res + " " + &val.to_string());
            let dist_cm = prediction.distance_context_map().iter().fold(::std::string::String::new(),
                                                                        |res, &val| res + " " + &val.to_string());
            let mixing_values = prediction.get_mixing_values().iter().fold(::std::string::String::new(),
                                                                           |res, &val| res + " " + &val.to_string());
            if prediction.has_context_speeds() {
                println_stderr!("prediction {} lcontextmap{} dcontextmap{} mixingvalues{} cmspeedinc {} {} cmspeedmax {} {} stspeedinc {} {} stspeedmax {} {} mxspeedinc {} {} mxspeedmax {} {}",
                                prediction_mode,
                                lit_cm,
                                dist_cm,
                                mixing_values,
                                prediction.context_map_speed()[0].0,
                                prediction.context_map_speed()[1].0,
                                prediction.context_map_speed()[0].1,
                                prediction.context_map_speed()[1].1,
                                prediction.stride_context_speed()[0].0,
                                prediction.stride_context_speed()[1].0,
                                prediction.stride_context_speed()[0].1,
                                prediction.stride_context_speed()[1].1,
                                prediction.combined_stride_context_speed()[0].0,
                                prediction.combined_stride_context_speed()[1].0,
                                prediction.combined_stride_context_speed()[0].1,
                                prediction.combined_stride_context_speed()[0].1,
                                );
            }else {
                println_stderr!("prediction {} lcontextmap{} dcontextmap{} mixingvalues{}",
                                prediction_mode,
                                lit_cm,
                                dist_cm,
                                mixing_values,
                );
            }
        },
        &interface::Command::Copy(ref copy) => {
            println_stderr!("copy {} from {}", copy.num_bytes, copy.distance);
        },
        &interface::Command::Dict(ref dict) => {
            let mut transformed_word = [0u8;38];
            let word_index = dict.word_id as usize * dict.word_size as usize +
                kBrotliDictionaryOffsetsByLength[dict.word_size as usize] as usize;
            let raw_word = &kBrotliDictionary[word_index..(word_index + dict.word_size as usize)];
            let actual_copy_len = TransformDictionaryWord(&mut transformed_word[..],
                                                          raw_word,
                                                          dict.word_size as i32,
                                                          dict.transform as i32) as usize;
            
            transformed_word.split_at(actual_copy_len).0;
            assert_eq!(dict.final_size as usize, actual_copy_len);
            println_stderr!("dict {} word {},{} {:x} func {} {:x}",
                            actual_copy_len,
                            dict.word_size,
                            dict.word_id,
                            SliceU8Ref(raw_word),
                            dict.transform,
                            SliceU8Ref(transformed_word.split_at(actual_copy_len).0));
        },
        &interface::Command::Literal(ref lit) => {
            println_stderr!("{} {} {:x}",
                            if lit.high_entropy {"rndins"} else {"insert"},
                            lit.data.slice().len(),
                            SliceU8Ref(lit.data.slice()));
        },
    }
}



// in-place thread create

use std::sync::RwLock;


pub struct MTJoinable<T:Send+'static, U:Send+'static>(JoinHandle<T>, PhantomData<U>);
#[cfg(not(feature="std"))]
impl<T:Send+'static, U:Send+'static+AnyBoxConstructor> Joinable<T, U> for MTJoinable<T, U> {
  fn join(self) -> Result<T, U> {
      match self.0.join() {
          Ok(t) => Ok(t),
          Err(_e) => Err(<U as AnyBoxConstructor>::new(())),
      }
  }
}
#[cfg(feature="std")]
impl<T:Send+'static, U:Send+'static+AnyBoxConstructor> Joinable<T, U> for MTJoinable<T, U> {
  fn join(self) -> Result<T, U> {
      match self.0.join() {
          Ok(t) => Ok(t),
          Err(e) => Err(<U as AnyBoxConstructor>::new(e)),
      }
  }
}
pub struct MTOwnedRetriever<U:Send+'static>(std::sync::Arc<RwLock<U>>);
impl<U:Send+'static> Clone for MTOwnedRetriever<U> {
    fn clone(&self) -> Self {
        MTOwnedRetriever(self.0.clone())
    }
}
impl<U:Send+'static> OwnedRetriever<U> for MTOwnedRetriever<U> {
  fn view<T, F:FnOnce(&U)-> T>(&self, f:F) -> Result<T, PoisonedThreadError> {
      match self.0.read() {
          Ok(u) => Ok(f(&*u)),
          Err(_) => Err(PoisonedThreadError::default()),
      }
  }
  fn unwrap(self) -> Result<U, PoisonedThreadError> {
    match std::sync::Arc::try_unwrap(self.0) {
      Ok(rwlock) => match rwlock.into_inner() {
        Ok(u) => Ok(u),
        Err(_) => Err(PoisonedThreadError::default()),
      },
      Err(_) => Err(PoisonedThreadError::default()),
    }
  }
}


#[derive(Default)]
pub struct MTSpawner{}


fn spawn_work<T:Send+'static,
              ExtraInput:Send+'static,
              F: Fn(ExtraInput, usize, usize, &U, Alloc) -> T+Send+'static,
              Alloc:BrotliAlloc+Send+'static,
              U:Send+'static+Sync>(
  extra_input: ExtraInput,
  index: usize,
  num_threads: usize,
  locked_input:MTOwnedRetriever<U>, alloc:Alloc, f:F) -> std::thread::JoinHandle<T>
where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  std::thread::spawn(move || {
      locked_input.view(move |guard:&U|->T {
          f(extra_input, index, num_threads, guard, alloc)
      }).unwrap()
  })
}

impl<T:Send+'static,
     ExtraInput:
     Send+'static,
     Alloc:BrotliAlloc+Send+'static,
     U:Send+'static+Sync> BatchSpawnable<T, ExtraInput, Alloc, U> for MTSpawner
where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  type JoinHandle = MTJoinable<T, BrotliEncoderThreadError>;
  type FinalJoinHandle = MTOwnedRetriever<U>;
    fn make_spawner(
    &mut self,
    input: &mut Owned<U>,
    ) -> Self::FinalJoinHandle {
      MTOwnedRetriever(std::sync::Arc::<RwLock<U>>::new(RwLock::new(mem::replace(input, Owned(InternalOwned::Borrowed)).unwrap())))
    }
    fn spawn<F: Fn(ExtraInput, usize, usize, &U, Alloc) -> T+Send+'static+Copy>(
      &mut self,
      locked_input: &mut Self::FinalJoinHandle,
      work:&mut SendAlloc<T, ExtraInput, Alloc, Self::JoinHandle>,
      index: usize,
      num_threads: usize,
      f: F,
    ) {
      let (alloc, extra_input) = work.replace_with_default();
      let ret = spawn_work(extra_input, index, num_threads, locked_input.clone(), alloc, f);
      *work = SendAlloc(InternalSendAlloc::Join(MTJoinable(ret, PhantomData::default())));
    }
}
impl<T:Send+'static,
     ExtraInput: Send+'static,
     Alloc:BrotliAlloc+Send+'static,
     U:Send+'static+Sync>
  BatchSpawnableLite<T, ExtraInput, Alloc, U> for MTSpawner
  where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  type JoinHandle = <MTSpawner as BatchSpawnable<T, ExtraInput, Alloc, U>>::JoinHandle;
  type FinalJoinHandle = <MTSpawner as BatchSpawnable<T, ExtraInput, Alloc, U>>::FinalJoinHandle;
  fn make_spawner(
    &mut self,
    input: &mut Owned<U>,
  ) -> Self::FinalJoinHandle {
     <Self as BatchSpawnable<T, ExtraInput, Alloc, U>>::make_spawner(self, input)
  }
  fn spawn(
    &mut self,
    handle:&mut Self::FinalJoinHandle,
    alloc_per_thread:&mut SendAlloc<T, ExtraInput, Alloc, Self::JoinHandle>,
    index: usize,
    num_threads: usize,
    f: fn(ExtraInput, usize, usize, &U, Alloc) -> T,
  ) {
   <Self as BatchSpawnable<T, ExtraInput, Alloc, U>>::spawn(self, handle, alloc_per_thread, index, num_threads, f)
  }
}

