/********************************************************************
 * test-qofsession.cpp: A Google Test suite for Qof Session.        *
 * Copyright 2016 Aaron Laws                                        *
 *                                                                  *
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, you can retrieve it from        *
 * https://www.gnu.org/licenses/old-licenses/gpl-2.0.html            *
 * or contact:                                                      *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 ********************************************************************/

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcpp"
#include <gtest/gtest.h>
#pragma GCC diagnostic pop

#include "../guid.hpp"
#include <qofsession.hpp>
#include <qof-backend.hpp>
#include <cstdlib>
#include "../gnc-backend-prov.hpp"
#include "../Account.h"

static QofBook * exported_book {nullptr};
static bool safe_sync_called {false};
static bool sync_called {false};
static bool load_error {true};
static bool data_loaded {false};

struct DestroyAccount
{
    void operator()(Account *acct)
    {
        xaccAccountBeginEdit (acct);
        xaccAccountDestroy (acct);
    }
};

using AccountPtr = std::unique_ptr<Account, DestroyAccount>;

class QofSessionMockBackend : public QofBackend
{
    AccountPtr m_root;
public:
    QofSessionMockBackend() = default;
    QofSessionMockBackend(const QofSessionMockBackend&) = delete;
    QofSessionMockBackend(const QofSessionMockBackend&&) = delete;
    virtual ~QofSessionMockBackend() = default;
    void session_begin(QofSession*, const char*, SessionOpenMode) {}
    void session_end() {}
    void load(QofBook*, QofBackendLoadType);
    void sync(QofBook*);
    void safe_sync(QofBook*);
    void export_coa(QofBook*);
};

void QofSessionMockBackend::load (QofBook *book, QofBackendLoadType)
{
    if (load_error)
        set_error(ERR_BACKEND_NO_BACKEND);
    else
        m_root = AccountPtr{gnc_account_create_root (book)};
    data_loaded = true;
}

void QofSessionMockBackend::safe_sync (QofBook *)
{
    safe_sync_called = true;
}

void QofSessionMockBackend::sync (QofBook *)
{
    sync_called = true;
}

void QofSessionMockBackend::export_coa(QofBook * book)
{
    exported_book = book;
}

static QofBackend*
test_backend_factory ()
{
    return new QofSessionMockBackend;
}

struct MockProvider : public QofBackendProvider
{
    MockProvider (char const * name, char const * access_method)
        : QofBackendProvider {name, access_method} {}
    QofBackend * create_backend (void) {return test_backend_factory ();}
    bool type_check (char const * type) {return true;}
};

static QofBackendProvider_ptr
get_provider ()
{
    return QofBackendProvider_ptr {new MockProvider {"Mock Backend", "file"}};
}

TEST (QofSessionTest, swap_books)
{
    qof_backend_register_provider (get_provider ());
    QofSession s1(qof_book_new());
    s1.begin ("book1", SESSION_NORMAL_OPEN);
    QofSession s2(qof_book_new());
    s2.begin ("book2", SESSION_NORMAL_OPEN);
    QofBook * b1 {s1.get_book ()};
    QofBook * b2 {s2.get_book ()};
    ASSERT_NE (b1, b2);
    s1.swap_books (s2);
    EXPECT_EQ (s1.get_book (), b2);
    EXPECT_EQ (s2.get_book (), b1);
    qof_backend_unregister_all_providers ();
}

TEST (QofSessionTest, ensure_all_data_loaded)
{
    qof_backend_register_provider (get_provider ());
    QofSession s(qof_book_new());
    s.begin ("book1", SESSION_NORMAL_OPEN);
    data_loaded = false;
    s.ensure_all_data_loaded ();
    EXPECT_EQ (data_loaded, true);
    qof_backend_unregister_all_providers ();
}

TEST (QofSessionTest, get_error)
{
    qof_backend_register_provider (get_provider ());
    QofSession s(qof_book_new());
    s.begin ("book1", SESSION_NORMAL_OPEN);
    s.ensure_all_data_loaded ();
    EXPECT_NE (s.get_error (), ERR_BACKEND_NO_ERR);
    //get_error should not clear the error.
    EXPECT_NE (s.get_error (), ERR_BACKEND_NO_ERR);
    qof_backend_unregister_all_providers ();
}

TEST (QofSessionTest, pop_error)
{
    qof_backend_register_provider (get_provider ());
    QofSession s(qof_book_new());
    s.begin ("book1", SESSION_NORMAL_OPEN);
    //We run the test first, and make sure there is an error condition.
    s.ensure_all_data_loaded ();
    EXPECT_NE (s.pop_error (), ERR_BACKEND_NO_ERR);
    EXPECT_EQ (s.get_error (), ERR_BACKEND_NO_ERR);
    qof_backend_unregister_all_providers ();
}

TEST (QofSessionTest, clear_error)
{
    qof_backend_register_provider (get_provider ());
    QofSession s(qof_book_new());
    s.begin ("book1", SESSION_NORMAL_OPEN);
    //We run the test first, and make sure there is an error condition.
    s.ensure_all_data_loaded ();
    EXPECT_NE (s.get_error (), ERR_BACKEND_NO_ERR);
    //Now we run it, and clear_error to make sure the error is actually cleared.
    s.ensure_all_data_loaded ();
    s.clear_error ();
    EXPECT_EQ (s.get_error (), ERR_BACKEND_NO_ERR);
    qof_backend_unregister_all_providers ();
}

TEST (QofSessionTest, load)
{
    /* We register a provider that gives a backend that throws an error on load.
     * This error during load should cause the qof session to destroy the book
     * and create a new one.
     */
    qof_backend_register_provider (get_provider ());
    QofSession s{qof_book_new()};
    s.begin ("book1", SESSION_NORMAL_OPEN);
    char *guidstr1 = guid_to_string(qof_instance_get_guid(s.get_book ()));
    s.load (nullptr);
    char *guidstr2 = guid_to_string(qof_instance_get_guid(s.get_book ()));
    EXPECT_STRNE (guidstr1, guidstr2);
    g_free(guidstr1);
    g_free(guidstr2);

    /* Now we'll do the load without returning an error from the backend,
     * and ensure that it's the new book from the previous test.
     */
    load_error = false;
    guidstr1 = guid_to_string(qof_instance_get_guid(s.get_book ()));
    s.load (nullptr);
    guidstr2 = guid_to_string(qof_instance_get_guid(s.get_book ()));
    EXPECT_STREQ (guidstr1, guidstr2);
    g_free(guidstr1);
    g_free(guidstr2);
    EXPECT_EQ (s.get_error(), ERR_BACKEND_NO_ERR);
    //But it's still empty, to the book shouldn't need saving
    EXPECT_FALSE(qof_book_session_not_saved (s.get_book ()));
    // I'll put load_error back just to be tidy.
    load_error = true;
    qof_backend_unregister_all_providers ();
}

TEST (QofSessionTest, save)
{
    qof_backend_register_provider (get_provider ());
    QofSession s(qof_book_new());
    s.begin ("book1", SESSION_NORMAL_OPEN);
    load_error = false;
    s.load (nullptr);
    qof_book_mark_session_dirty (s.get_book ());
    s.save (nullptr);
    EXPECT_EQ (sync_called, true);
    qof_backend_unregister_all_providers ();
    sync_called = false;
    load_error = true;
}

TEST (QofSessionTest, safe_save)
{
    qof_backend_register_provider (get_provider ());
    QofSession s(qof_book_new());
    s.begin ("book1", SESSION_NORMAL_OPEN);
    s.safe_save (nullptr);
    EXPECT_EQ (safe_sync_called, true);
    qof_backend_unregister_all_providers ();
    safe_sync_called = false;
}

TEST (QofSessionTest, export_session)
{
    qof_backend_register_provider (get_provider ());
    auto b1 = qof_book_new();
    QofSession s1(b1);
    s1.begin ("book1", SESSION_NORMAL_OPEN);
    qof_book_set_backend(b1, s1.get_backend());
    auto b2 = qof_book_new();
    QofSession s2(b2);
    s2.begin ("book2", SESSION_NORMAL_OPEN);
    qof_book_set_backend(b2, s2.get_backend());
    s2.export_session (s1, nullptr);
    EXPECT_EQ (exported_book, b1);

    qof_backend_unregister_all_providers ();
}
