#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>

// ==========================================
// 1. Book クラス（本）
// ==========================================
class Book {
private:
    std::string id;
    std::string title;
    std::string author;
    bool is_borrowed;

public:
    Book(std::string id, std::string title, std::string author)
        : id(id), title(title), author(author), is_borrowed(false) {}

    std::string getId() const { return id; }
    std::string getTitle() const { return title; }
    bool isBorrowed() const { return is_borrowed; }

    void borrowBook() { is_borrowed = true; }
    void returnBook() { is_borrowed = false; }

    void display() const {
        std::cout << "[" << id << "] " << title << " (" << author << ") - "
                  << (is_borrowed ? "貸出中" : "貸出可能") << std::endl;
    }
};

// ==========================================
// 2. User クラス（利用者）
// ==========================================
class User {
private:
    std::string id;
    std::string name;
    std::vector<std::shared_ptr<Book>> borrowed_books;
    const size_t MAX_BORROW_LIMIT = 3; // 最大貸出冊数制限

public:
    User(std::string id, std::string name) : id(id), name(name) {}

    std::string getId() const { return id; }
    std::string getName() const { return name; }

    bool canBorrow() const {
        return borrowed_books.size() < MAX_BORROW_LIMIT;
    }

    void addBook(std::shared_ptr<Book> book) {
        borrowed_books.push_back(book);
    }

    void removeBook(const std::string& book_id) {
        borrowed_books.erase(
            std::remove_if(borrowed_books.begin(), borrowed_books.end(),
                [&book_id](const std::shared_ptr<Book>& b) { return b->getId() == book_id; }),
            borrowed_books.end()
        );
    }

    void displayStatus() const {
        std::cout << "利用者: " << name << " (ID: " << id << ") | 貸出中: " 
                  << borrowed_books.size() << "/" << MAX_BORROW_LIMIT << "冊" << std::endl;
        for (const auto& book : borrowed_books) {
            std::cout << "  -> " << book->getTitle() << std::endl;
        }
    }
};

// ==========================================
// 3. Library クラス（図書館システム本体）
// ==========================================
class Library {
private:
    std::vector<std::shared_ptr<Book>> inventory;
    std::vector<std::shared_ptr<User>> users;

    // 内部検索用ヘルパー関数
    std::shared_ptr<Book> findBook(const std::string& book_id) {
        for (auto& book : inventory) {
            if (book->getId() == book_id) return book;
        }
        return nullptr;
    }

    std::shared_ptr<User> findUser(const std::string& user_id) {
        for (auto& user : users) {
            if (user->getId() == user_id) return user;
        }
        return nullptr;
    }

public:
    void addBook(std::shared_ptr<Book> book) { inventory.push_back(book); }
    void registerUser(std::shared_ptr<User> user) { users.push_back(user); }

    // 貸出処理（ビジネスロジック）
    void borrowBook(const std::string& user_id, const std::string& book_id) {
        auto user = findUser(user_id);
        auto book = findBook(book_id);

        std::cout << "\n--- 貸出要求: ユーザー[" << user_id << "] が 本[" << book_id << "] を要求 ---" << std::endl;

        if (!user || !book) {
            std::cout << "エラー: ユーザーまたは本が見つかりません。" << std::endl;
            return;
        }

        if (book->isBorrowed()) {
            std::cout << "エラー: 『" << book->getTitle() << "』は既に他の人に貸出中です。" << std::endl;
            return;
        }

        if (!user->canBorrow()) {
            std::cout << "エラー: " << user->getName() << " さんは貸出上限（3冊）に達しています。" << std::endl;
            return;
        }

        // 状態の更新（オブジェクト間の相互作用）
        book->borrowBook();
        user->addBook(book);
        std::cout << "成功: " << user->getName() << " さんに『" << book->getTitle() << "』を貸し出しました。" << std::endl;
    }

    // 返却処理
    void returnBook(const std::string& user_id, const std::string& book_id) {
        auto user = findUser(user_id);
        auto book = findBook(book_id);

        std::cout << "\n--- 返却要求: ユーザー[" << user_id << "] が 本[" << book_id << "] を返却 ---" << std::endl;

        if (!user || !book) {
            std::cout << "エラー: データに不整合があります。" << std::endl;
            return;
        }

        book->returnBook();
        user->removeBook(book_id);
        std::cout << "成功: 『" << book->getTitle() << "』が返却されました。" << std::endl;
    }

    // 状況表示
    void displayLibraryStatus() const {
        std::cout << "\n============= 図書館の現在のステータス =============" << std::endl;
        std::cout << "[蔵書一覧]" << std::endl;
        for (const auto& book : inventory) book->display();
        
        std::cout << "\n[利用者一覧]" << std::endl;
        for (const auto& user : users) user->displayStatus();
        std::cout << "==================================================" << std::endl;
    }
};

// ==========================================
// 4. メイン関数（シミュレーション実行）
// ==========================================
int main() {
    Library univ_library;

    // データの準備（本の追加）
    auto b1 = std::make_shared<Book>("B01", "オブジェクト指向設計論", "山田教授");
    auto b2 = std::make_shared<Book>("B02", "C++プログラミング詳解", "Schildt");
    auto b3 = std::make_shared<Book>("B03", "データ構造とアルゴリズム", "佐藤博士");
    auto b4 = std::make_shared<Book>("B04", "大学物理の基礎", "鈴木クヌース");
    
    univ_library.addBook(b1);
    univ_library.addBook(b2);
    univ_library.addBook(b3);
    univ_library.addBook(b4);

    // 利用者の登録
    auto u1 = std::make_shared<User>("U01", "アリス");
    auto u2 = std::make_shared<User>("U02", "ボブ");
    
    univ_library.registerUser(u1);
    univ_library.registerUser(u2);

    // 初期状態の表示
    univ_library.displayLibraryStatus();

    // 1. 通常の貸出
    univ_library.borrowBook("U01", "B01"); // アリスがオブジェクト指向を借りる
    univ_library.borrowBook("U01", "B02");
    
    // 2. 二重貸出のブロック（ボブがアリスと同じ本を借りようとする）
    univ_library.borrowBook("U02", "B01"); 

    // 3. 冊数制限のテスト（アリスが4冊目を借りようとする）
    univ_library.borrowBook("U01", "B03"); 
    univ_library.borrowBook("U01", "B04"); // 4冊目なのでエラーになるはず

    // 途中経過を表示
    univ_library.displayLibraryStatus();

    // 4. 返却処理と、その後の貸出再試行
    univ_library.returnBook("U01", "B01"); // アリスが本を返却
    univ_library.borrowBook("U02", "B01"); // ボブが借りられるようになる

    // 最終状態の表示
    univ_library.displayLibraryStatus();

    return 0;
}