use std::io;

use vfile::util::buf::previewer::BufPreviewer;

use crate::chopper::error::{Error, Result};
use crate::chopper::types::TimestampRange;
use crate::input::dir::factory::DirInputFactory;
use crate::input::input::{Input, InputType};
use crate::input::location::factory::LocationInputFactory;
use crate::source::source::Source;

pub type ChopperInputFactory = InputFactory<Box<dyn Source>, ()>;

pub struct InputFactory<S, EA> {
    dir_input_factory: Box<dyn DirInputFactory<S>>,
    loc_input_factory: LocationInputFactory<S, EA>,
}

impl<S, EA> InputFactory<S, EA> {
    pub fn new(
        dir_input_factory: Box<dyn DirInputFactory<S>>,
        loc_input_factory: LocationInputFactory<S, EA>,
    ) -> Result<Self> {
        Ok(InputFactory {
            dir_input_factory,
            loc_input_factory,
        })
    }

    /// this will be able to handle a subset of input locations compared to create_source();
    /// i.e. some sources do not come from a single underlying file that can be read directly
    pub fn create_previewer(&self, input: &Input, range: &TimestampRange) -> Result<BufPreviewer> {
        //TODO: should this decompress first? probably needs explicit fn arg to enable/disable?
        match &input.input {
            InputType::Location(loc) => self.loc_input_factory.create_previewer(&loc, range),
            InputType::StdIn => {
                return Ok(BufPreviewer::new(io::stdin())?);
            }
        }
    }

    pub fn create_source(
        &self,
        input: &Input,
        range: &TimestampRange,
        extra_args: EA,
    ) -> Result<S> {
        // first get stdin out of the way, since it doesn't need a transport
        let loc = match &input.input {
            InputType::Location(path) => path,
            InputType::StdIn => {
                let previewer = BufPreviewer::new(io::stdin())?;
                return self.loc_input_factory.create_source_from_previewer(
                    previewer,
                    None,
                    &input.format,
                    extra_args,
                );
            }
        };

        // next we want to see if this is handled by the dir input factory
        if let Some(source) =
            self.dir_input_factory
                .create_source(loc, range, input.format.clone())?
        {
            return Ok(source);
        };

        // finally let the single file input factory handle it
        let single_source =
            self.loc_input_factory
                .create_source(loc, range, &input.format, extra_args)?;
        if let Some(source) = single_source {
            return Ok(source);
        }

        Err(Error::Io(io::Error::new(
            io::ErrorKind::Other,
            format!("failed to handle path {}", loc),
        )))
    }
}
