// Copyright (C) 2023 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Task notification data structures.
//!
//! Defines `NotifyData`, `Progress`, `EachFileStatus`, `SubscribeType`, and
//! related types used to report task progress, file status, and lifecycle
//! events to subscribed clients.

use std::collections::HashMap;

use super::config::{Action, Version};
use super::info::State;
use super::reason::Reason;
use crate::FileSpec;

/// Types of events that can be subscribed to for task notifications.
///
/// Used to specify which events a client wants to receive callbacks for.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum SubscribeType {
    /// Task has completed successfully.
    Complete = 0,
    /// Task has failed to complete.
    Fail,
    /// Response headers have been received.
    HeaderReceive,
    /// Task has been paused.
    Pause,
    /// Task progress has updated.
    Progress,
    /// Task has been removed.
    Remove,
    /// Task has been resumed.
    Resume,
    /// System fault has occurred.
    FaultOccur = 8,
}

/// Reasons why a task might be waiting to run.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum WaitingCause {
    /// Task is waiting in the queue for its turn.
    TaskQueue = 0,
    /// Task is waiting for network connectivity.
    Network,
    /// Task is waiting due to application state constraints.
    AppState,
    /// Task is waiting due to user state constraints.
    UserState,
}

/// Contains task notification data sent to subscribers.
#[derive(Debug, Clone)]
pub(crate) struct NotifyData {
    /// Bundle name of the application that created the task.
    pub(crate) bundle: String,
    /// Current progress information.
    pub(crate) progress: Progress,
    /// Action type (download/upload).
    pub(crate) action: Action,
    /// API version used for this task.
    pub(crate) version: Version,
    /// Status of each file in the task.
    pub(crate) each_file_status: Vec<EachFileStatus>,
    /// Unique task identifier.
    pub(crate) task_id: u32,
    /// User ID that owns the task.
    pub(crate) uid: u64,
}

/// Core progress information shared across different components.
#[repr(C)]
#[derive(Clone, Debug)]
pub(crate) struct CommonProgress {
    /// Current state of the task as a raw byte value.
    pub(crate) state: u8,
    /// Index of the current file being processed.
    pub(crate) index: usize,
    /// Total number of bytes processed across all files.
    pub(crate) total_processed: usize,
}

/// Comprehensive progress information for a task.
#[derive(Debug, Clone)]
pub(crate) struct Progress {
    /// Core progress metadata.
    pub(crate) common_data: CommonProgress,
    /// Total size of each file in bytes.
    /// A value of -1 indicates unknown size.
    pub(crate) sizes: Vec<i64>,
    /// Number of bytes processed for each file.
    pub(crate) processed: Vec<usize>,
    /// Additional progress-related parameters.
    pub(crate) extras: HashMap<String, String>,
}

/// Status information for an individual file in a multi-file task.
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct EachFileStatus {
    /// Path to the file.
    pub(crate) path: String,
    /// Reason code for the file's current status.
    pub(crate) reason: Reason,
    /// Human-readable status message.
    pub(crate) message: String,
}

impl EachFileStatus {
    /// Creates a list of `EachFileStatus` objects for a set of file
    /// specifications.
    ///
    /// Assigns the provided reason to files at or after the specified index,
    /// and `Reason::Default` to files before the index.
    ///
    /// # Arguments
    /// * `file_specs` - List of file specifications to create statuses for
    /// * `index` - Starting index for applying the provided reason
    /// * `reason` - Reason to apply to files at or after the index
    pub(crate) fn create_each_file_status(
        file_specs: &[FileSpec],
        index: usize,
        reason: Reason,
    ) -> Vec<EachFileStatus> {
        let mut vec = Vec::new();
        for (i, file_spec) in file_specs.iter().enumerate() {
            // Only apply the provided reason to files we're actively processing
            let code = if i >= index { reason } else { Reason::Default };
            let each_file_status = EachFileStatus {
                path: file_spec.path.clone(),
                reason: code,
                message: code.to_str().into(),
            };
            vec.push(each_file_status);
        }
        vec
    }
}

impl Progress {
    /// Creates a new `Progress` instance with the specified file sizes.
    ///
    /// Initializes all files to have processed 0 bytes and sets the state to
    /// `State::Initialized`.
    ///
    /// # Arguments
    /// * `sizes` - List of file sizes in bytes (-1 indicates unknown size)
    pub(crate) fn new(sizes: Vec<i64>) -> Self {
        let len = sizes.len();
        Progress {
            common_data: CommonProgress {
                state: State::Initialized.repr,
                index: 0,
                total_processed: 0,
            },
            sizes,
            processed: vec![0; len],
            extras: HashMap::<String, String>::new(),
        }
    }

    /// Checks if the task has finished processing all files.
    ///
    /// Returns `true` only if:
    /// 1. All files have known sizes (no -1 values)
    /// 2. The sum of processed bytes equals the sum of total sizes
    pub(crate) fn is_finish(&self) -> bool {
        self.sizes.iter().all(|a| *a != -1)
            && self.processed.iter().sum::<usize>() == self.sizes.iter().sum::<i64>() as usize
    }
}