/***********************************************************************\
 * Wzorzec projektowy puli.                Konrad Grochowski, nr 192621 *
 *                                                                      *
 * Zastosowania:                                                        *
 *           - Zarządzanie grupą obiektów reprezentujących              *
 *             zasoby wielokrotnego użycia                              *
 *           - Ograniczenie kosztów tworzenia i usuwania obiektów       *
 *                                                                      *
 * Opis:                                                                *
 *        Wzorzec realizowany jest zgodnie ze schematem przedstawionym  *
 *        poniżej. Obiekt Klient wykorzystuje obiekt PulaObiektow jako  *
 *        fabrykę dla ObiektWielokrotnegoUzytku a po zakończeniu        *
 *        interakcji z uzyskanym obiektem zwraca go do puli, gdzie jest *
 *        on przywracany do stanu "czystego" i moze nadawać do          *
 *        ponownego użytku przez innego klienta puli. Dzięki temu       *
 *        zminimalizowany jest czas poświęcony na alokację zasobów, co  *
 *        może mieć duże znaczenie np. w przypadku zasobów systemowych  *
 *        lub bardzo dużych obiektów. Użytkownik jest także zwolniony   *
 *        z odpowiedzialności za poprawne zakończenie życia obiektu -   *
 *        odpowiada za to pula.                                         *
 *        Zachowanie puli może być dostosowane do specyficznych         *
 *        zastosowań. Może mieć stały lub zmienny (ograniczony od góry) *
 *        rozmiar, może tworzyć obiekty gdy jest to konieczne, lub      *
 *        zaalokować wszystkie od razu.                                 *
 *                                                                      *
 *     /--------\ 0...n                                                 *
 *     | Klient |---------------------------------------                *
 *     \--------/                                       |               *
 *         |                                           \|/ 1            *
 *                                             /------------------\     *
 *         |                                   |   PulaObiektow   |     *
 *                                             |------------------|     *
 *         |                                   |  pobierzObiekt() |     *
 *                                             |  zwolnijObiekt() |     *
 *         |                                   \------------------/     *
 *                                                      /\              *
 *         |                                            \/ 1            *
 *                                                      |               *
 *        \|/                                           |               *
 *     /---------------------------\                    |               *
 *     | ObiektWielokrotnegoUzytku |                    |               *
 *     |---------------------------|/___________________|               *
 *     |        wyczysc()          |\ 0...n                             *
 *     \---------------------------/                                    *
 *                                                                      *
 \**********************************************************************/
#include <cassert>
#include <vector>
#include <iostream>
/**
 * Obiekt ktory bedzie skladnikiem puli.
 * W tym przykładzie jest to obiekt reprezentujący
 * wiadomość jakiegoś protokołu, wymagającą dużego buforu
 * na dane.
 */
class Message
{
public:
  // Ten rozmiar powoduje ze nie opłaca się
  // realokować obiektów tego typu za każdym razem gdy są potrzebne
  static const unsigned int MSG_SIZE = 100000000;

  Message() { reset(); }
  virtual ~Message() {}

  // Tutaj są funkcje użytkowe
  int getUserId() const { return m_userId; }
  void setUserId(int userId_) { m_userId = userId_; }
  const char* getMessageBuffer() const { return m_messageBuffer; }
  char* getMessageBuffer() { return m_messageBuffer; }

  // A to funkcja używana przez pulę do "wyczyszczenia" stanu obiektu
  void reset();

private:
  int m_userId;
  char m_messageBuffer[MSG_SIZE];
};

/**
 * Klasa reprezentująca samą pulę.
 * Tworzy obiekty od razu w konstruktorze - potem szybciej można
 * przetwarzać wiadomości. Można dać dodatkowy parametr pozwalający
 * na powiększanie się puli do pewnego limitu (tzw. "wskaźnik wysokiej wody"
 * ang. "high water mark")
 */
class MessagePool
{
public:
  static const unsigned int FIXED_SIZE = 0;

  /**
   * Tworzy pulę o podanym rozmiarze i ewentualnym rozmiarze max.
   */
  MessagePool(unsigned int initialSize_,
              unsigned int maxSize_ = FIXED_SIZE);
  virtual ~MessagePool();

  /* Zwraca jeden z elementów puli, lub NULL jeśli pula nie ma więcej elementów
  * O ile to możliwe powiększa pulę o nowy obiekt (jeśli pula miałą podany
  * drugi parametr w konstruktorze
  */
  Message* takeMessage();
  /** Przyjmuje obiekt z powrotem do puli i czyści jego stan.
   * Każdy obiekt pobrany musi być zwrócony do niej tą metodą
   */
  void releaseMessage(Message* msg);

private:
  typedef std::vector<Message*> MessageVector;
  unsigned int m_maxSize, m_actualSize;
  MessageVector m_messageVector;
};

int main(int argc, char * argv[])
{
  using namespace std;
  MessagePool pool(2); // mala pula dla przykładu
  Message* m1 = pool.takeMessage();
  Message* m2 = pool.takeMessage();
  Message* m3 = pool.takeMessage();

  // 3 wskaźnik jest NULLem - bo pula sie skończyła
  cout << "M1: " << m1 << " M2: " << m2 << " M3: " << m3 << endl;

  // symulacja odczytu wiadomości z protokołu np sieciowego
  memset(m1->getMessageBuffer(),'A',511);
  m2->getMessageBuffer()[512] = '\0';
  // wypisanie - "użycie" - wiadomości
  cout << "M1 Message: " << m1->getMessageBuffer() << endl;
  // zwolnienie użytej już wiadomości
  pool.releaseMessage(m1);

  // pobranie obiektu na nową wiadomość
  m3 = pool.takeMessage();
  // to jest ten sam obiekt co za pierwszym razem
  cout << "M1==M3: " << (m1==m3) << endl;
  // ale wyczyszczony i gotowy do ponownego użycia
  cout << "M3 Message: " << m3->getMessageBuffer() << endl;

  // czyszczenie na koniec
  pool.releaseMessage(m2);
  pool.releaseMessage(m3);

  return 0;
}

void Message::reset()
{
  m_userId = -1;
  m_messageBuffer[0] = 0;
}

MessagePool::MessagePool(unsigned int initialSize_,
                         unsigned int maxSize_)
  : m_maxSize(maxSize_ != FIXED_SIZE ? maxSize_ : initialSize_)
{
  m_messageVector.reserve(m_maxSize);
  for(unsigned int i = 0; i < initialSize_; ++i)
  {
    Message* m = new Message();
    m_messageVector.push_back(m);
  }
  m_actualSize = m_messageVector.size();
}

MessagePool::~MessagePool()
{
  // ważne sprawdzenie, czy użytkownicy wszystko oddają co wzieli
  assert(m_messageVector.size() == m_actualSize);

  for(MessageVector::iterator i = m_messageVector.begin();
      i != m_messageVector.end();
      ++i)
    delete *i;
}

Message* MessagePool::takeMessage()
{
  if(m_messageVector.size() > 0)
  {
    Message* ret = m_messageVector.back();
    m_messageVector.pop_back();
    return ret;
  }

  if(m_actualSize == m_maxSize) return NULL;

  ++m_actualSize;
  return new Message();
}

void MessagePool::releaseMessage(Message* msg)
{
  if(!msg) return;
  assert(m_actualSize >= m_messageVector.size());
  msg->reset();
  m_messageVector.push_back(msg);
}
