/********************************************************
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 *
 * Released to the public domain, use at your own risk!
 ********************************************************/

namespace System.Data.SQLite
{
  using System;
  using System.Data.Common;

#if !PLATFORM_COMPACTFRAMEWORK
  using System.Reflection;
  using System.Runtime.Serialization;
  using System.Security.Permissions;
#endif

  /// <summary>
  /// SQLite exception class.
  /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
  [Serializable()]
  public sealed class SQLiteException : DbException, ISerializable
#else
  public sealed class SQLiteException : Exception
#endif
  {
    private SQLiteErrorCode _errorCode;

#if !PLATFORM_COMPACTFRAMEWORK
    /// <summary>
    /// Private constructor for use with serialization.
    /// </summary>
    /// <param name="info">
    /// Holds the serialized object data about the exception being thrown.
    /// </param>
    /// <param name="context">
    /// Contains contextual information about the source or destination.
    /// </param>
    private SQLiteException(SerializationInfo info, StreamingContext context)
      : base(info, context)
    {
      _errorCode = (SQLiteErrorCode)info.GetInt32("errorCode");
    }
#endif

    /// <summary>
    /// Public constructor for generating a SQLite exception given the error
    /// code and message.
    /// </summary>
    /// <param name="errorCode">
    /// The SQLite return code to report.
    /// </param>
    /// <param name="message">
    /// Message text to go along with the return code message text.
    /// </param>
    public SQLiteException(SQLiteErrorCode errorCode, string message)
      : base(GetStockErrorMessage(errorCode, message))
    {
      _errorCode = errorCode;
    }

    /// <summary>
    /// Public constructor that uses the base class constructor for the error
    /// message.
    /// </summary>
    /// <param name="message">Error message text.</param>
    public SQLiteException(string message)
      : this(SQLiteErrorCode.Unknown, message)
    {
    }

    /// <summary>
    /// Public constructor that uses the default base class constructor.
    /// </summary>
    public SQLiteException()
    {
    }

    /// <summary>
    /// Public constructor that uses the base class constructor for the error
    /// message and inner exception.
    /// </summary>
    /// <param name="message">Error message text.</param>
    /// <param name="innerException">The original (inner) exception.</param>
    public SQLiteException(string message, Exception innerException)
      : base(message, innerException)
    {
    }

#if !PLATFORM_COMPACTFRAMEWORK
    /// <summary>
    /// Adds extra information to the serialized object data specific to this
    /// class type.  This is only used for serialization.
    /// </summary>
    /// <param name="info">
    /// Holds the serialized object data about the exception being thrown.
    /// </param>
    /// <param name="context">
    /// Contains contextual information about the source or destination.
    /// </param>
    [SecurityPermission(
      SecurityAction.LinkDemand,
      Flags = SecurityPermissionFlag.SerializationFormatter)]
    public override void GetObjectData(
      SerializationInfo info,
      StreamingContext context)
    {
      if (info != null)
        info.AddValue("errorCode", _errorCode);

      base.GetObjectData(info, context);
    }
#endif

    /// <summary>
    /// Gets the associated SQLite result code for this exception as a
    /// <see cref="SQLiteErrorCode" />.  This property returns the same
    /// underlying value as the <see cref="ErrorCode" /> property.
    /// </summary>
    public SQLiteErrorCode ResultCode
    {
      get { return _errorCode; }
    }

    /// <summary>
    /// Gets the associated SQLite return code for this exception as an
    /// <see cref="Int32" />.  For desktop versions of the .NET Framework,
    /// this property overrides the property of the same name within the
    /// <see cref="System.Runtime.InteropServices.ExternalException" />
    /// class.  This property returns the same underlying value as the
    /// <see cref="ResultCode" /> property.
    /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
    public override int ErrorCode
#else
    public int ErrorCode
#endif
    {
      get { return (int)_errorCode; }
    }

    /// <summary>
    /// Returns the error message for the specified SQLite return code.
    /// </summary>
    /// <param name="errorCode">The SQLite return code.</param>
    /// <returns>The error message or null if it cannot be found.</returns>
    private static string GetErrorString(
        SQLiteErrorCode errorCode
        )
    {
#if !PLATFORM_COMPACTFRAMEWORK
        //
        // HACK: This must be done via reflection in order to prevent
        //       the RuntimeHelpers.PrepareDelegate method from over-
        //       eagerly attempting to locate the new (and optional)
        //       sqlite3_errstr() function in the SQLite core library
        //       because it happens to be in the static call graph for
        //       the AppDomain.DomainUnload event handler registered
        //       by the SQLiteLog class.
        //
        BindingFlags flags = BindingFlags.Static |
            BindingFlags.NonPublic | BindingFlags.InvokeMethod;

        return typeof(SQLiteBase).InvokeMember("GetErrorString",
            flags, null, null, new object[] { errorCode }) as string;
#else
        return SQLiteBase.GetErrorString(errorCode);
#endif
    }

    /// <summary>
    /// Returns the composite error message based on the SQLite return code
    /// and the optional detailed error message.
    /// </summary>
    /// <param name="errorCode">The SQLite return code.</param>
    /// <param name="message">Optional detailed error message.</param>
    /// <returns>Error message text for the return code.</returns>
    private static string GetStockErrorMessage(
        SQLiteErrorCode errorCode,
        string message
        )
    {
        return String.Format("{0}{1}{2}",
            GetErrorString(errorCode),
#if !NET_COMPACT_20
            Environment.NewLine, message).Trim();
#else
            "\r\n", message).Trim();
#endif
    }
  }

  /// <summary>
  /// SQLite error codes.  Actually, this enumeration represents a return code,
  /// which may also indicate success in one of several ways (e.g. SQLITE_OK,
  /// SQLITE_ROW, and SQLITE_DONE).  Therefore, the name of this enumeration is
  /// something of a misnomer.
  /// </summary>
  public enum SQLiteErrorCode
  {
    /// <summary>
    /// The error code is unknown.  This error code
    /// is only used by the managed wrapper itself.
    /// </summary>
    Unknown = -1,
    /// <summary>
    /// Successful result
    /// </summary>
    Ok /* 0 */,
    /// <summary>
    /// SQL error or missing database
    /// </summary>
    Error /* 1 */,
    /// <summary>
    /// Internal logic error in SQLite
    /// </summary>
    Internal /* 2 */,
    /// <summary>
    /// Access permission denied
    /// </summary>
    Perm /* 3 */,
    /// <summary>
    /// Callback routine requested an abort
    /// </summary>
    Abort /* 4 */,
    /// <summary>
    /// The database file is locked
    /// </summary>
    Busy /* 5 */,
    /// <summary>
    /// A table in the database is locked
    /// </summary>
    Locked /* 6 */,
    /// <summary>
    /// A malloc() failed
    /// </summary>
    NoMem /* 7 */,
    /// <summary>
    /// Attempt to write a readonly database
    /// </summary>
    ReadOnly /* 8 */,
    /// <summary>
    /// Operation terminated by sqlite3_interrupt()
    /// </summary>
    Interrupt /* 9 */,
    /// <summary>
    /// Some kind of disk I/O error occurred
    /// </summary>
    IoErr /* 10 */,
    /// <summary>
    /// The database disk image is malformed
    /// </summary>
    Corrupt /* 11 */,
    /// <summary>
    /// Unknown opcode in sqlite3_file_control()
    /// </summary>
    NotFound /* 12 */,
    /// <summary>
    /// Insertion failed because database is full
    /// </summary>
    Full /* 13 */,
    /// <summary>
    /// Unable to open the database file
    /// </summary>
    CantOpen /* 14 */,
    /// <summary>
    /// Database lock protocol error
    /// </summary>
    Protocol /* 15 */,
    /// <summary>
    /// Database is empty
    /// </summary>
    Empty /* 16 */,
    /// <summary>
    /// The database schema changed
    /// </summary>
    Schema /* 17 */,
    /// <summary>
    /// String or BLOB exceeds size limit
    /// </summary>
    TooBig /* 18 */,
    /// <summary>
    /// Abort due to constraint violation
    /// </summary>
    Constraint /* 19 */,
    /// <summary>
    /// Data type mismatch
    /// </summary>
    Mismatch /* 20 */,
    /// <summary>
    /// Library used incorrectly
    /// </summary>
    Misuse /* 21 */,
    /// <summary>
    /// Uses OS features not supported on host
    /// </summary>
    NoLfs /* 22 */,
    /// <summary>
    /// Authorization denied
    /// </summary>
    Auth /* 23 */,
    /// <summary>
    /// Auxiliary database format error
    /// </summary>
    Format /* 24 */,
    /// <summary>
    /// 2nd parameter to sqlite3_bind out of range
    /// </summary>
    Range /* 25 */,
    /// <summary>
    /// File opened that is not a database file
    /// </summary>
    NotADb /* 26 */,
    /// <summary>
    /// Notifications from sqlite3_log()
    /// </summary>
    Notice /* 27 */,
    /// <summary>
    /// Warnings from sqlite3_log()
    /// </summary>
    Warning /* 28 */,
    /// <summary>
    /// sqlite3_step() has another row ready
    /// </summary>
    Row = 100,
    /// <summary>
    /// sqlite3_step() has finished executing
    /// </summary>
    Done, /* 101 */
    /// <summary>
    /// Used to mask off extended result codes
    /// </summary>
    NonExtendedMask = 0xFF
  }
}
