#include <iostream>
using namespace std;

// simple linked list of floats with insert at back and remove from front
class LList {
   protected:
      struct lnode {
         float data;
         lnode* next;
      };
      lnode *front;
      lnode *back;
      int size;

   public:
       // inline default constructor
       // e.g. LList L1;
       LList() { front = nullptr; back = nullptr; size = 0; }

       // copy constructor
       // e.g. LList L2(L1);
       LList(const LList& orig);

       // move constructor
       // e.g. LList L3 = std::move(L1);
       LList(LList&& orig);

       // destructor, automatic
       ~LList();

       // swap method
       // e.g. L1.swap(L2);
       void swap(LList &other);

       // overloaded operators
       // e.g. L3 = L1 + L2;
       LList operator=(LList rhs);
       friend LList operator+(const LList& lhs, const LList& rhs);

       //  basic insert at back, remove from front, print
       //  e.g. L1.insertB(25.7);
       void insertB(float d);
       //  e.g. float f;
       //       if (L1.removeF(f)) ...
       bool removeF(float &d);
       //  e.g. L1.print();
       void print();

};


LList::LList(LList &&orig)
{
   front = orig.front;
   back = orig.back;
   size = orig.size;
   orig.front = nullptr;
   orig.back = nullptr;
   orig.size = 0;
}

void LList::insertB(float d)
{
   lnode* n = new lnode;
   n->data = d;
   n->next = nullptr;
   if (back) back->next = n;
   else front = n;
   back = n;
   size++;
}

bool LList::removeF(float &d)
{
   if (!front) return false;
   d = front->data;
   lnode* n = front;
   front = front->next;
   delete n;
   size--;
   if (size == 0) back = nullptr;
   return true;
}

void LList::print()
{
   cout << "List of " << size << " items:";
   lnode* curr = front;
   while (curr) {
      cout << " " << curr->data;
      curr = curr->next;
   }
   cout << endl;
}

void LList::swap(LList& other)
{
    using std::swap;
    swap(front, other.front);
    swap(back, other.back);
    swap(size, other.size);
}

LList::LList(const LList& other)
{
   front = nullptr;
   back = nullptr;
   size = 0;
   lnode* curr = other.front;
   while (curr) {
      insertB(curr->data);
      curr = curr->next;
   }
}

LList LList::operator=(LList rhs)
{
   this->swap(rhs);
   return *this;
}

LList::~LList()
{
   cout << "Deallocating:";
   float d;
   while (removeF(d)) cout << " " << d;
   cout << endl;
}

LList operator+(const LList& lhs, const LList& rhs)
{
   LList temp = lhs;
   LList::lnode* curr = rhs.front;
   while (curr) {
      temp.insertB(curr->data);
      curr = curr->next;
   }
   return temp;
}

int main()
{
   LList L1, L2, L4;
   L1.insertB(10);
   L1.insertB(20);
   L1.insertB(30);
   cout << "L1: "; L1.print();
   L2.insertB(0.5);
   L2.insertB(0.005);
   cout << "L2: "; L2.print();
// output from this block of code:
// L1: List of 3 items: 10 20 30
// L2: List of 2 items: 0.5 0.005

   cout << "\nMaking L3 a copy of L1 using copy constructor, LList L3(L1);"<< endl;
   LList L3(L1);
   cout << "and printing afterwards..." << endl;
   cout << "L1: "; L1.print();
   cout << "L2: "; L2.print();
   cout << "L3: "; L3.print();
// output from this block of code:
// Making L3 a copy of L1 using copy constructor, LList L3(L1);
// and printing afterwards...
// L1: List of 3 items: 10 20 30
// L2: List of 2 items: 0.5 0.005
// L3: List of 3 items: 10 20 30

   cout << "\nSwapping L1 and L2 using L1.swap(L2)" << endl;
   L1.swap(L2);
   cout << "and printing afterwards..." << endl;
   cout << "L1: "; L1.print();
   cout << "L2: "; L2.print();
   cout << "L3: "; L3.print();
// output from this block of code:
// Swapping L1 and L2 using L1.swap(L2)
// and printing afterwards...
// L1: List of 2 items: 0.5 0.005
// L2: List of 3 items: 10 20 30
// L3: List of 3 items: 10 20 30

   cout << "\nInserting 1 into L1, 2 into L2, 3 into L3" << endl;
   L1.insertB(1);
   L2.insertB(2);
   L3.insertB(3);
   cout << "and printing afterwards..." << endl;
   cout << "L1: "; L1.print();
   cout << "L2: "; L2.print();
   cout << "L3: "; L3.print();
// output from this block of code:
// Inserting 1 into L1, 2 into L2, 3 into L3
// and printing afterwards...
// L1: List of 3 items: 0.5 0.005 1
// L2: List of 4 items: 10 20 30 2
// L3: List of 4 items: 10 20 30 3

   cout << "\nAssigning L4 = L1" << endl;
   L4 = L1;
   cout << "and printing afterwards..." << endl;
   cout << "L1: "; L1.print();
   cout << "L2: "; L2.print();
   cout << "L3: "; L3.print();
   cout << "L4: "; L4.print();
// output from this block of code:
// Assigning L4 = L1
// Deallocating: 0.5 0.005 1
// Deallocating:
// and printing afterwards...
// L1: List of 3 items: 0.5 0.005 1
// L2: List of 4 items: 10 20 30 2
// L3: List of 4 items: 10 20 30 3
// L4: List of 3 items: 0.5 0.005 1

   cout << "\nMoving L1 to L3 using L3 = std::move(L1)" << endl;
   L3 = std::move(L1);
   cout << "and printing afterwards..." << endl;
   cout << "L1: "; L1.print();
   cout << "L2: "; L2.print();
   cout << "L3: "; L3.print();
   cout << "L4: "; L4.print();
   cout << endl;
// output from this block of code:
// Moving L1 to L3 using L3 = std::move(L1)
// Deallocating: 0.5 0.005 1
// Deallocating: 10 20 30 3
// and printing afterwards...
// L1: List of 0 items:
// L2: List of 4 items: 10 20 30 2
// L3: List of 3 items: 0.5 0.005 1
// L4: List of 3 items: 0.5 0.005 1

   cout << "\nMaking L4 = L2 + L3" << endl;
   L4 = L2 + L3;
   cout << "and printing afterwards..." << endl;
   cout << "L1: "; L1.print();
   cout << "L2: "; L2.print();
   cout << "L3: "; L3.print();
   cout << "L4: "; L4.print();
   cout << endl;
// output from this block of code:
// Making L4 = L2 + L3
// Deallocating: 10 20 30 2 0.5 0.005 1
// Deallocating: 0.5 0.005 1
// and printing afterwards...
// L1: List of 0 items:
// L2: List of 4 items: 10 20 30 2
// L3: List of 3 items: 0.5 0.005 1
// L4: List of 7 items: 10 20 30 2 0.5 0.005 1

   cout << "\nEnd of program, destructors about to run" << endl;
// output from this block of code:
// End of program, destructors about to run
// Deallocating: 0.5 0.005 1
// Deallocating: 10 20 30 2 0.5 0.005 1
// Deallocating: 10 20 30 2
// Deallocating:
}

