/*
* Copyright 2023, Ali Arya
* Anyone Can Code: Algorithmic Thinking
* Example 6.11c
*/

//simple program demonstrating class hierarchy

#include <stdio.h>
#include <iostream>
using namespace std;

#define MAX_BOOKS   3
#define MAX_PATRONS 3

#define BOOK_TYPE       1
#define PERIODICAL_TYPE 2
#define EBOOK_TYPE      3
#define DISC_TYPE       4

/*
* Classes:
•	Items
    •   PrintedItem
        •	Book
        •	Periodical
    •	DigitalItem
        •	E-Book
        •	Disc
•	Users
    •	Staff
    •	Patron

*/

//===========================================================================

//Base class for all objects
class Entity
{
protected:
    int ID;
public:
    Entity()
    {
        ID = -1;
    }
    Entity(int i)
    {
        ID = i;
    }
    int GetID()
    {
        return ID;
    }
    void Report()
    {
        cout << "\n";
        cout << "ID: " << ID << ", ";
    }
};
class Item : public Entity
{
protected:
    int type;
    int borrower;
    
public:
    Item()
    {
        //not initialized
        type = -1;
        borrower = -1;
    }
    Item(int i, int t) : Item()
    {
        //initialize
        ID = i;
        type= t;
    }
    void Report()
    {
        Entity::Report();
        cout << "Type: " << type << ", ";
        cout << "Borrower: " << borrower << ", ";
    }    
    bool Available()
    {
        if(borrower == -1)
            return true;
        else
            return false;
    }
    int GetBorrower()
    {
        return borrower;
    }
    bool Borrow(int b)
    {   
        if(borrower == -1 && b != -1)
        {
            borrower = b;
            return true;
        }
        else
            return false;
    }
    void Return()
    {
        borrower = -1;
    } 
};
class PrintedItem : public Item
{
protected:
    int numPages;
public:
    PrintedItem()
    {
        numPages = -1;
    }
    PrintedItem(int i, int t, int n) : Item(i,t), numPages(n)
    {
    }
    void Report()
    {
        Item::Report();
        cout << "Number of Pages: " << numPages << ", ";
    }    
};
class Book : public PrintedItem
{
protected:
    char author[30];
public:
    Book()
    {
        author[0] = NULL;
    }
    Book(int i, int t, int n, char* name) : PrintedItem(i, t, n)
    {
        strcpy_s(author, 30, name);
    }
    void Report()
    {
        PrintedItem::Report();
        cout << "Author: " << author << ", ";
    }    
    void OtherBooks()
    {
        cout << "other books by the sam author\n";
    }    
};
class Periodical : public PrintedItem
{

};
class DigitalItem : public Item
{

};
class Ebook : public DigitalItem
{

};
class Disc : public DigitalItem
{

};

//===========================================================================

class User : public Entity
{
protected:
    
public:
    User()
    {
    }
    User(int i) : User()
    {
        ID = i;
    }
    void Report()
    {
        Entity::Report(); //we don't really need this overridden function but maybe in future we add members to this class
    }
};
class Staff : public User
{

};
class Patron : public User
{
protected:
    int item;
public:
    Patron()
    {
        item = -1;
    }
    Patron(int i) : User(i), item(-1)
    {
    }
    bool Available()
    {
        if(item == -1)
            return true;
        else
            return false;
    }
    int GetItem()
    {
        return item;
    }
    bool Borrow(int i)
    {   
        if(item == -1 && i != -1)
        {
            item = i;
            return true;
        }
        else
            return false;
    }
    void Return()
    {
        item = -1;
    } 
    void Report()
    {
        User::Report();
        cout << "Borrowed Item: " << item << ", ";
    }
};

//===========================================================================

class Library
{
protected:
    Book* books[MAX_BOOKS];
    Patron* patrons[MAX_PATRONS];
    int numBooks;
    int numPatrons;

public:
    Library()
    {
        for (int i = 0; i < MAX_BOOKS; i++)
            books[i] = new Book(1000 + i, BOOK_TYPE, rand() % 900 + 100, (char*)"name");
        numBooks = MAX_BOOKS;
        for (int i = 0; i < MAX_PATRONS; i++)
            patrons[i] = new Patron(1000 + i);
        numPatrons = MAX_PATRONS;
    }
    void Report()
    {
        cout << "\n\n=========================\n>> BOOKS:\n";
        for (int i = 0; i < MAX_BOOKS; i++)
        {
            if (books[i] != NULL)
                books[i]->Report();
        }
        cout << "\n\n=========================\n>> PATRONS:\n";
        for (int i = 0; i < MAX_PATRONS; i++)
        {
            if (patrons[i] != NULL)
                patrons[i]->Report();
        }
        cout << "\n\n=========================\n";
    }
    void RemoveBook(int id)
    {
        for (int i = 0; i < MAX_BOOKS; i++)
        {
            if (books[i] != NULL)
            {
                if (books[i]->GetID() == id)
                {
                    if (books[i]->Available() == false)
                    {
                        int id = books[i]->GetBorrower();
                        Patron* p = FindPatron(id);
                        p->Return();
                        books[i]->Return();
                    }
                    delete books[i];
                    books[i] = NULL;
                }
                    
            }
        }
    }
    void AddBook()
    {
        for (int i = 0; i < MAX_BOOKS; i++)
        {
            if (books[i] == NULL) //empty location
                books[i] = new Book(1000 + numBooks++, BOOK_TYPE, rand() % 900 + 100, (char*)"name");
        }
    }
    Book* FindBook(int id)
    {
        for (int i = 0; i < MAX_BOOKS; i++)
        {
            if (books[i] != NULL)
            {
                if (books[i]->GetID() == id)
                {
                    return books[i];
                }
            }
        }
        return NULL;
    }
    void RemovePatron(int id)
    {
        for (int i = 0; i < MAX_PATRONS; i++)
        {
            if (patrons[i] != NULL)
            {
                if (patrons[i]->GetID() == id)
                {
                    if (patrons[i]->Available() == false)
                    {
                        int id = patrons[i]->GetItem();
                        Book* b = FindBook(id);
                        b->Return();
                        patrons[i]->Return();
                    }
                    delete patrons[i];
                    patrons[i] = NULL;
                }

            }
        }
    }
    void AddPatron()
    {
        for (int i = 0; i < MAX_PATRONS; i++)
        {
            if (patrons[i] == NULL) //empty location
                patrons[i] = new Patron(1000 + numPatrons++);
        }
    }
    Patron* FindPatron(int id)
    {
        for (int i = 0; i < MAX_PATRONS; i++)
        {
            if (patrons[i] != NULL)
            {
                if (patrons[i]->GetID() == id)
                {
                    return patrons[i];
                }
            }
        }
        return NULL;
    }
    bool Borrow(int p, int b)
    {
        Patron* patron = FindPatron(p);
        Book* book = FindBook(b);
        if (patron != NULL && book != NULL)
        {
            if (patron->Available() && book->Available())
            {
                patron->Borrow(b);
                book->Borrow(p);
                return true;
            }
        }
        return false;
    }
    bool Return(int p, int b)
    {
        Patron* patron = FindPatron(p);
        Book* book = FindBook(b);
        if (patron != NULL && book != NULL)
        {
            if (patron->GetItem() == b && book->GetBorrower() == p)
            {
                patron->Return();
                book->Return();
                return true;
            }
        }
        return false;
    }
    void Run()
    {
        bool quit = false;

        while (!quit)
        {
            int command;
            int p;
            int b;
            cout << "1-Report, 2-Borrow, 3-Return, 0-Exit \n";
            cin >> command;
            if (command == 0)
                quit = true;
            else if (command == 1)
            {
                Report();
            }
            else if (command == 2)
            {
                cout << "Enter patron ID: ";
                cin >> p;
                cout << "Enter book ID: ";
                cin >> b;
                if (Borrow(p, b))
                    cout << "Dond\n";
                else
                    cout << "Sorry\n";
                Report();
            }
            else if (command == 3)
            {
                cout << "Enter patron ID: ";
                cin >> p;
                cout << "Enter book ID: ";
                cin >> b;
                if (Return(p,b))
                    cout << "Dond\n";
                else
                    cout << "Sorry\n";
                Report();
            }
            else
                cout << "Invalid command\n";
        }
    }
};
//===========================================================================

int main()
{
    Library lib;
    lib.Report();

    lib.Borrow(1000, 1000);
    lib.Report();

    lib.RemoveBook(1000);
    lib.Report();

    lib.AddBook();
    lib.Report();

    lib.Run();

    return 0;
}