/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::cmp::{max, min};
use std::ops::Range;
use std::path::PathBuf;
use std::time::SystemTime;

use base::id::WebViewId;
use embedder_traits::FilterPattern;
use ipc_channel::ipc::IpcSender;
use malloc_size_of_derive::MallocSizeOf;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::blob_url_store::{BlobBuf, BlobURLStoreError};

// HACK: Not really process-safe now, we should send Origin
//       directly instead of this in future, blocked on #11722
/// File manager store entry's origin
pub type FileOrigin = String;

/// A token modulating access to a file for a blob URL.
pub enum FileTokenCheck {
    /// Checking against a token not required,
    /// used for accessing a file
    /// that isn't linked to from a blob URL.
    NotRequired,
    /// Checking against token required.
    Required(Uuid),
    /// Request should always fail,
    /// used for cases when a check is required,
    /// but no token could be acquired.
    ShouldFail,
}

/// Relative slice positions of a sequence,
/// whose semantic should be consistent with (start, end) parameters in
/// <https://w3c.github.io/FileAPI/#dfn-slice>
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RelativePos {
    /// Relative to first byte if non-negative,
    /// relative to one past last byte if negative,
    pub start: i64,
    /// Relative offset from first byte if Some(non-negative),
    /// relative to one past last byte if Some(negative),
    /// None if one past last byte
    pub end: Option<i64>,
}

impl RelativePos {
    /// Full range from start to end
    pub fn full_range() -> RelativePos {
        RelativePos {
            start: 0,
            end: None,
        }
    }

    /// Instantiate optional slice position parameters
    pub fn from_opts(start: Option<i64>, end: Option<i64>) -> RelativePos {
        RelativePos {
            start: start.unwrap_or(0),
            end,
        }
    }

    /// Slice the inner sliced range by repositioning
    pub fn slice_inner(&self, rel_pos: &RelativePos) -> RelativePos {
        RelativePos {
            start: self.start + rel_pos.start,
            end: match (self.end, rel_pos.end) {
                (Some(old_end), Some(rel_end)) => Some(old_end + rel_end),
                (old, None) => old,
                (None, rel) => rel,
            },
        }
    }

    /// Compute absolute range by giving the total size
    /// <https://w3c.github.io/FileAPI/#slice-method-algo>
    pub fn to_abs_range(&self, size: usize) -> Range<usize> {
        let size = size as i64;

        let start = {
            if self.start < 0 {
                max(size + self.start, 0)
            } else {
                min(self.start, size)
            }
        };

        let end = match self.end {
            Some(rel_end) => {
                if rel_end < 0 {
                    max(size + rel_end, 0)
                } else {
                    min(rel_end, size)
                }
            },
            None => size,
        };

        let span: i64 = max(end - start, 0);

        Range {
            start: start.to_usize().unwrap(),
            end: (start + span).to_usize().unwrap(),
        }
    }

    // Per <https://fetch.spec.whatwg.org/#concept-scheme-fetch> step 3.blob.14.8:
    // "A range header denotes an inclusive byte range, while the slice blob algorithm input range does not.
    // To use the slice blob algorithm, we have to increment rangeEnd."
    pub fn to_abs_blob_range(&self, size: usize) -> Range<usize> {
        let orig_range = self.to_abs_range(size);
        let start = orig_range.start;
        let end = usize::min(orig_range.end + 1, size);
        Range { start, end }
    }
}

/// Response to file selection request
#[derive(Debug, Deserialize, Serialize)]
pub struct SelectedFile {
    pub id: Uuid,
    pub filename: PathBuf,
    pub modified: SystemTime,
    pub size: u64,
    // https://w3c.github.io/FileAPI/#dfn-type
    pub type_string: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub enum FileManagerThreadMsg {
    /// Select a single file. Last field is pre-selected file path for testing
    SelectFile(
        WebViewId,
        Vec<FilterPattern>,
        IpcSender<FileManagerResult<SelectedFile>>,
        FileOrigin,
        Option<PathBuf>,
    ),

    /// Select multiple files. Last field is pre-selected file paths for testing
    SelectFiles(
        WebViewId,
        Vec<FilterPattern>,
        IpcSender<FileManagerResult<Vec<SelectedFile>>>,
        FileOrigin,
        Option<Vec<PathBuf>>,
    ),

    /// Read FileID-indexed file in chunks, optionally check URL validity based on boolean flag
    ReadFile(
        IpcSender<FileManagerResult<ReadFileProgress>>,
        Uuid,
        FileOrigin,
    ),

    /// Add an entry as promoted memory-based blob
    PromoteMemory(Uuid, BlobBuf, bool, FileOrigin),

    /// Add a sliced entry pointing to the parent FileID, and send back the associated FileID
    /// as part of a valid Blob URL
    AddSlicedURLEntry(
        Uuid,
        RelativePos,
        IpcSender<Result<Uuid, BlobURLStoreError>>,
        FileOrigin,
    ),

    /// Decrease reference count and send back the acknowledgement
    DecRef(Uuid, FileOrigin, IpcSender<Result<(), BlobURLStoreError>>),

    /// Activate an internal FileID so it becomes valid as part of a Blob URL
    ActivateBlobURL(Uuid, IpcSender<Result<(), BlobURLStoreError>>, FileOrigin),

    /// Revoke Blob URL and send back the acknowledgement
    RevokeBlobURL(Uuid, FileOrigin, IpcSender<Result<(), BlobURLStoreError>>),
}

#[derive(Debug, Deserialize, Serialize)]
pub enum ReadFileProgress {
    Meta(BlobBuf),
    Partial(Vec<u8>),
    EOF,
}

pub type FileManagerResult<T> = Result<T, FileManagerThreadError>;

#[derive(Debug, Deserialize, Serialize)]
pub enum FileManagerThreadError {
    /// The selection action is invalid due to exceptional reason
    InvalidSelection,
    /// The selection action is cancelled by user
    UserCancelled,
    /// Errors returned from file system request
    FileSystemError(String),
    /// Blob URL Store error
    BlobURLStoreError(BlobURLStoreError),
}