using System;
using System.Diagnostics;

namespace iiie.Logs.DBO
{
    /// <summary>
    /// Represent the result of an operation, with details in case of failure and data in case of success
    /// </summary>
    /// <typeparam name="T">The data type</typeparam>
    public class OpResult<T>
    {
        /// <summary>
        /// Status of the operation
        /// </summary>
        public ResultStatus Status { get; set; }

        /// <summary>
        /// True if Status is Success, false otherwise
        /// </summary>
        public bool Success
        {
            get
            {
                return Status == ResultStatus.Success;
            }
        }

        /// <summary>
        /// Details on the error, or null if error details must not be shown to the user
        /// Only relevent when Success is false
        /// </summary>
        public string PublicDetails { get; set; }

        /// <summary>
        /// Technical details on the error
        /// Only relevent when Success is false
        /// </summary>
        public string PrivateDetails { get; set; }

        public StackFrame[] StackFrames { get; set; }

        /// <summary>
        /// The data returned by the call
        /// Only relevent if Success is true
        /// </summary>
        public T Data { get; set; }

        /// <summary>
        /// Create and returns a new OpResult with
        /// {Public,Private}Details = null, Data = data, Status = true
        /// Used for successfull operations
        /// </summary>
        public static OpResult<T> Ok(T data)
        {
            return new OpResult<T>
            {
                PublicDetails = null,
                PrivateDetails = null,
                StackFrames = new StackTrace().GetFrames(),
                Data = data,
                Status = ResultStatus.Success
            };
        }

        /// <summary>
        /// Create and returns a new OpResult with
        /// PrivateDetails = privateDetails, Data = null, Status = status
        /// If publicDetails == "", privateDetails will be used
        /// Used for all errors (invalid input, ...)
        /// </summary>
        public static OpResult<T> Error(ResultStatus status, string privateDetails, string publicDetails = null)
        {
            return new OpResult<T>
            {
                PublicDetails = publicDetails == "" ? privateDetails : publicDetails,
                PrivateDetails = privateDetails,
                StackFrames = new StackTrace().GetFrames(),
                Data = default(T),
                Status = status
            };
        }

        /// <summary>
        /// Create and returns a new OpResult with
        /// PrivateDetails = e to string, Data = null, Status = status
        /// If publicDetails == "", privateDetails will be used
        /// Used for all errors (invalid input, ...)
        /// </summary>
        public static OpResult<T> Error(ResultStatus status, Exception e, string userDetails = null)
        {
            string message = "";
            Exception inner = e;
            while (inner != null)
            {
                message += inner.GetType().Name + ": " + inner.Message + (inner.InnerException != null ? "\n" : "");
                inner = inner.InnerException;
            }
            return Error(status, message, userDetails);
        }

        /// <summary>
        /// Translate an error to another type
        /// </summary>
        /// <typeparam name="T2">The original type</typeparam>
        /// <param name="other">The original error</param>
        /// <returns>The original error with the type T</returns>
        public static OpResult<T> Error<T2>(OpResult<T2> other)
        {
            return new OpResult<T>
            {
                PublicDetails = other.PublicDetails,
                PrivateDetails = other.PrivateDetails,
                StackFrames = other.StackFrames,
                Data = default(T),
                Status = other.Status
            };
        }

        /// <summary>
        /// Translate an error to another type
        /// </summary>
        /// <typeparam name="T2">The original type</typeparam>
        /// <returns>The original error with the type T</returns>
        public OpResult<T2> To<T2>()
        {
            return OpResult<T2>.Error(this);
        }

        /// <summary>
        /// Implicit converion to bool
        /// </summary>
        /// <param name="res">The result to be converted</param>
        /// <returns>True if Success, false otherwise</returns>
        public static implicit operator bool(OpResult<T> res)
        {
            return res.Success;
        }

        protected OpResult()
        {
        }
    }
}
