use std::thread;
use std::thread::JoinHandle;

use anyhow::Context;
use crossbeam::channel::{Receiver, Sender};

use crate::candidate_selection::hashing::{FileHasher, HashInstructions};
use crate::candidate_selection::messages::WorkerMessage::{HashComputed, NotHashable};
use crate::candidate_selection::messages::{
    CoordinatorMessage, FileWithDescriptor, HashComputedResult, WorkerMessage,
};

pub struct Worker {
    work_in: Receiver<CoordinatorMessage>,
    work_out: Sender<WorkerMessage>,
    file_hasher: FileHasher,
}

impl Worker {
    // response message should change into an actual message (split offs needs to be respected)
    pub fn new(
        work_in: Receiver<CoordinatorMessage>,
        work_out: Sender<WorkerMessage>,
        buffer_size: usize,
    ) -> Self {
        let file_hasher = FileHasher::new(buffer_size);

        Worker {
            work_in,
            work_out,
            file_hasher,
        }
    }

    pub fn work(mut self) -> JoinHandle<::anyhow::Result<Worker>> {
        thread::spawn(move || {
            self.work_internal()?;
            Ok(self)
        })
    }

    // We are allowed to open up to 512 file handles here
    fn work_internal(&mut self) -> ::anyhow::Result<()> {
        while let Ok(CoordinatorMessage::HashCommand(file_with_handle, instructions)) =
            self.work_in.recv()
        {
            self.hash(file_with_handle, instructions)?;
        }
        Ok(())
    }

    fn hash(
        &mut self,
        file_with_handle: FileWithDescriptor,
        instructions: HashInstructions,
    ) -> ::anyhow::Result<()> {
        let mut file_with_handle = file_with_handle;

        match self
            .file_hasher
            .hash(&mut file_with_handle.file, &instructions)
        {
            Ok(hash) => {
                let result = HashComputedResult { instructions, hash };

                self.work_out
                    .send(HashComputed(file_with_handle, result))
                    .context("Worker failed to send hash result")
            }
            Err(_) => self
                .work_out
                .send(NotHashable(file_with_handle))
                .context("Worker failed to send not hashable message"),
        }?;

        Ok(())
    }
}
