extern crate mime;
use std::fs::File;
use std::io::{self, Error as IoError, Read};
#[cfg(feature = "send3")]
use std::mem;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[cfg(feature = "send2")]
use self::mime::APPLICATION_OCTET_STREAM;
use chrono::{DateTime, Duration, Utc};
use mime_guess::{self, Mime};
use openssl::symm::encrypt_aead;
#[cfg(feature = "send2")]
use reqwest::blocking::multipart::{Form, Part};
#[cfg(feature = "send2")]
use reqwest::blocking::Request;
#[cfg(feature = "send2")]
use reqwest::header::AUTHORIZATION;
use reqwest::Error as ReqwestError;
#[cfg(feature = "send3")]
use serde_json;
use url::{ParseError as UrlParseError, Url};
#[cfg(feature = "send3")]
use websocket::{result::WebSocketError, OwnedMessage};
#[cfg(feature = "send2")]
use super::params::Params;
use super::params::{Error as ParamsError, ParamsData};
use super::password::{Error as PasswordError, Password};
#[cfg(feature = "send2")]
use crate::api::nonce::header_nonce;
#[cfg(feature = "send2")]
use crate::api::request::ensure_success;
use crate::api::request::ResponseError;
use crate::api::Version;
use crate::client::Client;
use crate::crypto::b64;
use crate::crypto::key_set::KeySet;
#[cfg(feature = "send3")]
use crate::file::info::FileInfo;
use crate::file::metadata::Metadata;
use crate::file::remote_file::RemoteFile;
#[cfg(feature = "send3")]
use crate::io::ChunkRead;
#[cfg(feature = "send2")]
use crate::pipe::crypto::GcmCrypt;
#[cfg(feature = "send3")]
use crate::pipe::crypto::{ece, EceCrypt};
use crate::pipe::{
    prelude::*,
    progress::{ProgressPipe, ProgressReporter},
};
pub struct Upload {
    
    version: Version,
    
    host: Url,
    
    path: PathBuf,
    
    
    
    name: Option<String>,
    
    password: Option<String>,
    
    params: Option<ParamsData>,
}
impl Upload {
    
    pub fn new(
        version: Version,
        host: Url,
        path: PathBuf,
        name: Option<String>,
        password: Option<String>,
        params: Option<ParamsData>,
    ) -> Self {
        Self {
            version,
            host,
            path,
            name,
            password,
            params,
        }
    }
    
    pub fn invoke(
        self,
        client: &Client,
        reporter: Option<&Arc<Mutex<dyn ProgressReporter>>>,
    ) -> Result<RemoteFile, Error> {
        
        let file = FileData::from(&self.path)?;
        let key = KeySet::generate(true);
        
        let reader = self.create_reader(&key, reporter.cloned())?;
        let reader_len = reader.len_in() as u64;
        
        if let Some(reporter) = reporter {
            reporter
                .lock()
                .map_err(|_| UploadError::Progress)?
                .start(reader_len);
        }
        
        let (result, nonce) = match self.version {
            #[cfg(feature = "send2")]
            Version::V2 => self.upload_send2(client, &key, &file, reader)?,
            #[cfg(feature = "send3")]
            Version::V3 => self.upload_send3(client, &key, &file, reader)?,
        };
        
        if let Some(reporter) = reporter {
            reporter.lock().map_err(|_| UploadError::Progress)?.finish();
        }
        
        if let Some(password) = self.password {
            Password::new(&result, &password, nonce.clone()).invoke(client)?;
        }
        
        #[cfg(feature = "send2")]
        {
            #[allow(unreachable_patterns)]
            match self.version {
                Version::V2 => {
                    if let Some(mut params) = self.params {
                        params.normalize(self.version);
                        if !params.is_empty() {
                            Params::new(&result, params, nonce.clone()).invoke(client)?;
                        }
                    }
                }
                _ => {}
            }
        }
        Ok(result)
    }
    
    #[cfg(feature = "send2")]
    fn create_metadata(&self, key: &KeySet, file: &FileData) -> Result<Vec<u8>, MetaError> {
        
        let name = self.name.clone().unwrap_or_else(|| file.name().to_owned());
        
        let metadata = Metadata::from_send2(key.iv(), name, &file.mime())
            .to_json()
            .into_bytes();
        
        let mut metadata_tag = vec![0u8; 16];
        let mut metadata = match encrypt_aead(
            KeySet::cipher(),
            key.meta_key().unwrap(),
            Some(&[0u8; 12]),
            &[],
            &metadata,
            &mut metadata_tag,
        ) {
            Ok(metadata) => metadata,
            Err(_) => return Err(MetaError::Encrypt),
        };
        
        metadata.append(&mut metadata_tag);
        Ok(metadata)
    }
    
    #[cfg(feature = "send3")]
    fn create_file_info(&self, key: &KeySet, file: &FileData) -> Result<String, MetaError> {
        
        let name = self.name.clone().unwrap_or_else(|| file.name().to_owned());
        
        let mime = format!("{}", file.mime());
        let metadata = Metadata::from_send3(name, mime, file.size())
            .to_json()
            .into_bytes();
        
        let mut metadata_tag = vec![0u8; 16];
        let mut metadata = match encrypt_aead(
            KeySet::cipher(),
            key.meta_key().unwrap(),
            Some(&[0u8; 12]),
            &[],
            &metadata,
            &mut metadata_tag,
        ) {
            Ok(metadata) => metadata,
            Err(_) => return Err(MetaError::Encrypt),
        };
        
        metadata.append(&mut metadata_tag);
        
        let expiry = self.params.as_ref().and_then(|p| p.expiry_time);
        let downloads = self.params.as_ref().and_then(|p| p.download_limit);
        
        Ok(FileInfo::from(expiry, downloads, b64::encode(&metadata), key).to_json())
    }
    
    fn create_reader(
        &self,
        key: &KeySet,
        reporter: Option<Arc<Mutex<dyn ProgressReporter>>>,
    ) -> Result<Reader, Error> {
        
        let file = match File::open(self.path.as_path()) {
            Ok(file) => file,
            Err(err) => return Err(FileError::Open(err).into()),
        };
        
        let len = file
            .metadata()
            .expect("failed to fetch file metadata")
            .len();
        
        let progress = ProgressPipe::zero(len, reporter);
        let reader = progress.reader(Box::new(file));
        
        match self.version {
            #[cfg(feature = "send2")]
            Version::V2 => {
                let encrypt = GcmCrypt::encrypt(len as usize, key.file_key().unwrap(), key.iv());
                let reader = encrypt.reader(Box::new(reader));
                Ok(Reader::new(Box::new(reader)))
            }
            #[cfg(feature = "send3")]
            Version::V3 => {
                let ikm = key.secret().to_vec();
                let encrypt = EceCrypt::encrypt(len as usize, ikm, None);
                let reader = encrypt.reader(Box::new(reader));
                Ok(Reader::new(Box::new(reader)))
            }
        }
    }
    
    #[cfg(feature = "send2")]
    fn upload_send2(
        &self,
        client: &Client,
        key: &KeySet,
        file: &FileData,
        reader: Reader,
    ) -> Result<(RemoteFile, Option<Vec<u8>>), Error> {
        
        let metadata = self.create_metadata(&key, file)?;
        
        let req = self.create_request_send2(client, &key, &metadata, reader)?;
        
        self.execute_request_send2(req, client, &key)
            .map_err(|e| e.into())
    }
    
    #[cfg(feature = "send2")]
    fn create_request_send2(
        &self,
        client: &Client,
        key: &KeySet,
        metadata: &[u8],
        reader: Reader,
    ) -> Result<Request, UploadError> {
        
        let len = reader.len_out() as u64;
        
        let part = Part::reader_with_length(reader, len)
            .mime_str(APPLICATION_OCTET_STREAM.as_ref())
            .expect("failed to set request mime");
        let form = Form::new().part("data", part);
        
        let url = self.host.join("api/upload")?;
        
        client
            .post(url.as_str())
            .header(
                AUTHORIZATION.as_str(),
                format!("send-v1 {}", key.auth_key_encoded().unwrap()),
            )
            .header("X-File-Metadata", b64::encode(&metadata))
            .multipart(form)
            .build()
            .map_err(|_| UploadError::Request)
    }
    
    
    #[cfg(feature = "send2")]
    fn execute_request_send2(
        &self,
        req: Request,
        client: &Client,
        key: &KeySet,
    ) -> Result<(RemoteFile, Option<Vec<u8>>), UploadError> {
        
        let response = match client.execute(req) {
            Ok(response) => response,
            
            Err(_) => return Err(UploadError::Request),
        };
        
        ensure_success(&response).map_err(UploadError::Response)?;
        
        let nonce = header_nonce(&response).ok();
        
        let response: UploadResponse = match response.json() {
            Ok(response) => response,
            Err(err) => return Err(UploadError::Decode(err)),
        };
        
        Ok((response.into_file(self.host.clone(), &key, None)?, nonce))
    }
    
    #[cfg(feature = "send3")]
    fn upload_send3(
        &self,
        client: &Client,
        key: &KeySet,
        file_data: &FileData,
        mut reader: Reader,
    ) -> Result<(RemoteFile, Option<Vec<u8>>), Error> {
        
        let ws_url = self
            .host
            .join("api/ws")
            .map_err(|e| Error::Upload(e.into()))?;
        
        let mut client = client
            .websocket(ws_url.as_str())
            .map_err(|_| Error::Upload(UploadError::Request))?;
        
        let file_info = self
            .create_file_info(&key, file_data)
            .map_err(|e| -> Error { e.into() })?;
        let ws_metadata = OwnedMessage::Text(file_info);
        client
            .send_message(&ws_metadata)
            .map_err(|e| Error::Upload(e.into()))?;
        
        let result = client
            .recv_message()
            .map_err(|_| Error::Upload(UploadError::InvalidResponse))?;
        let upload_response: UploadResponse = match result {
            OwnedMessage::Text(ref data) => serde_json::from_str(data)
                .map_err(|_| Error::Upload(UploadError::InvalidResponse))?,
            _ => return Err(UploadError::InvalidResponse.into()),
        };
        
        let mut header = vec![0u8; ece::HEADER_LEN as usize];
        reader
            .read_exact(&mut header)
            .expect("failed to read header from reader");
        client
            .send_message(&OwnedMessage::Binary(header))
            .map_err(|e| Error::Upload(e.into()))?;
        
        let result =
            reader
                .chunks(ece::RS as usize)
                .fold(None, |result: Option<UploadError>, chunk| {
                    
                    if result.is_some() {
                        return result;
                    }
                    
                    let message = OwnedMessage::Binary(chunk.expect("invalid chunk"));
                    client.send_message(&message).err().map(|e| e.into())
                });
        if let Some(err) = result {
            return Err(err.into());
        }
        
        client
            .send_message(&OwnedMessage::Binary(vec![0]))
            .map_err(|e| Error::Upload(e.into()))?;
        
        let status = match client
            .recv_message()
            .map_err(|_| Error::Upload(UploadError::InvalidResponse))?
        {
            OwnedMessage::Text(status) => Some(status),
            _ => None,
        };
        let ok = status
            .and_then(|s| serde_json::from_str::<UploadStatusResponse>(&s).ok())
            .map(|s| s.is_ok())
            .unwrap_or(false);
        if !ok {
            return Err(UploadError::Response(ResponseError::Undefined).into());
        }
        
        let _ = client.shutdown();
        mem::drop(client);
        
        let remote_file = upload_response.into_file(
            self.host.clone(),
            &key,
            self.params.as_ref().and_then(|p| {
                p.expiry_time
                    .map(|s| Utc::now() + Duration::seconds(s as i64))
            }),
        )?;
        Ok((remote_file, None))
    }
}
#[derive(Debug, Deserialize)]
struct UploadResponse {
    
    id: String,
    
    
    url: String,
    
    
    
    #[serde(alias = "ownerToken", alias = "owner")]
    owner_token: String,
}
impl UploadResponse {
    
    
    
    pub fn into_file(
        self,
        host: Url,
        key: &KeySet,
        expiry_time: Option<DateTime<Utc>>,
    ) -> Result<RemoteFile, UploadError> {
        Ok(RemoteFile::new(
            self.id,
            Some(Utc::now()),
            expiry_time,
            host,
            Url::parse(&self.url)?,
            key.secret().to_vec(),
            Some(self.owner_token),
        ))
    }
}
#[derive(Debug, Deserialize)]
#[cfg(feature = "send3")]
struct UploadStatusResponse {
    
    ok: bool,
}
#[cfg(feature = "send3")]
impl UploadStatusResponse {
    
    pub fn is_ok(&self) -> bool {
        self.ok
    }
}
struct FileData<'a> {
    
    name: &'a str,
    
    mime: Mime,
    
    #[allow(unused)]
    size: u64,
}
impl<'a> FileData<'a> {
    
    pub fn from(path: &'a PathBuf) -> Result<Self, FileError> {
        
        if !path.is_file() {
            return Err(FileError::NotAFile);
        }
        
        let name = match path.file_name() {
            Some(name) => name.to_str().unwrap_or("file"),
            None => "file",
        };
        
        let size = path.metadata()?.len();
        Ok(Self {
            name,
            mime: mime_guess::from_path(path).first_or_octet_stream(),
            size,
        })
    }
    
    pub fn name(&self) -> &str {
        self.name
    }
    
    pub fn mime(&self) -> &Mime {
        &self.mime
    }
    
    #[cfg(feature = "send3")]
    pub fn size(&self) -> u64 {
        self.size
    }
}
struct Reader {
    
    inner: Box<dyn ReadLen>,
}
impl Reader {
    
    pub fn new(inner: Box<dyn ReadLen>) -> Self {
        Self { inner }
    }
}
impl Read for Reader {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.inner.read(buf)
    }
}
impl PipeLen for Reader {
    fn len_in(&self) -> usize {
        self.inner.len_in()
    }
    fn len_out(&self) -> usize {
        self.inner.len_out()
    }
}
#[derive(Fail, Debug)]
pub enum Error {
    
    #[fail(display = "failed to prepare uploading the file")]
    Prepare(#[cause] PrepareError),
    
    
    
    #[fail(display = "")]
    File(#[cause] FileError),
    
    #[fail(display = "failed to upload the file")]
    Upload(#[cause] UploadError),
    
    #[fail(display = "failed to change file parameters")]
    Params(#[cause] ParamsError),
    
    #[fail(display = "failed to set the password")]
    Password(#[cause] PasswordError),
}
impl From<MetaError> for Error {
    fn from(err: MetaError) -> Error {
        Error::Prepare(PrepareError::Meta(err))
    }
}
impl From<FileError> for Error {
    fn from(err: FileError) -> Error {
        Error::File(err)
    }
}
impl From<ReaderError> for Error {
    fn from(err: ReaderError) -> Error {
        Error::Prepare(PrepareError::Reader(err))
    }
}
impl From<UploadError> for Error {
    fn from(err: UploadError) -> Error {
        Error::Upload(err)
    }
}
impl From<ParamsError> for Error {
    fn from(err: ParamsError) -> Error {
        Error::Params(err)
    }
}
impl From<PasswordError> for Error {
    fn from(err: PasswordError) -> Error {
        Error::Password(err)
    }
}
#[derive(Fail, Debug)]
pub enum PrepareError {
    
    #[fail(display = "failed to prepare file metadata")]
    Meta(#[cause] MetaError),
    
    
    #[fail(display = "failed to access the file to upload")]
    Reader(#[cause] ReaderError),
    
    #[fail(display = "failed to create uploader client")]
    Client,
}
#[derive(Fail, Debug)]
pub enum MetaError {
    
    #[fail(display = "failed to encrypt file metadata")]
    Encrypt,
}
#[derive(Fail, Debug)]
pub enum ReaderError {
    
    #[fail(display = "failed to create file encryptor")]
    Encrypt,
    
    
    #[fail(display = "failed to create progress reader")]
    Progress,
}
#[derive(Fail, Debug)]
pub enum FileError {
    
    #[fail(display = "the given path is not an existing file")]
    NotAFile,
    
    #[fail(display = "failed to open the file to upload")]
    Open(#[cause] IoError),
}
impl From<IoError> for FileError {
    fn from(err: IoError) -> FileError {
        FileError::Open(err)
    }
}
#[derive(Fail, Debug)]
pub enum UploadError {
    
    
    #[fail(display = "failed to update upload progress")]
    Progress,
    
    #[fail(display = "failed to request file upload")]
    Request,
    
    
    #[fail(display = "failed to stream file for upload over websocket")]
    #[cfg(feature = "send3")]
    UploadStream(#[cause] WebSocketError),
    
    
    #[fail(display = "got invalid response from server")]
    InvalidResponse,
    
    #[fail(display = "bad response from server for uploading")]
    Response(#[cause] ResponseError),
    
    
    #[fail(display = "failed to decode upload response")]
    Decode(#[cause] ReqwestError),
    
    #[fail(display = "failed to parse received URL")]
    ParseUrl(#[cause] UrlParseError),
}
#[cfg(feature = "send3")]
impl From<WebSocketError> for UploadError {
    fn from(err: WebSocketError) -> UploadError {
        UploadError::UploadStream(err)
    }
}
impl From<ResponseError> for UploadError {
    fn from(err: ResponseError) -> UploadError {
        UploadError::Response(err)
    }
}
impl From<UrlParseError> for UploadError {
    fn from(err: UrlParseError) -> UploadError {
        UploadError::ParseUrl(err)
    }
}