#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>
#include <utility>

using namespace std;

/*
    ============================================================
    SHALLOW COPY VS DEEP COPY
    ============================================================

    Shallow Copy:
    - Copies the pointer address only.
    - The original object and copied object share the same memory.
    - Changing data through one object affects the other object.
    - Dangerous with destructors because both objects may try to delete
      the same memory.

    Deep Copy:
    - Allocates new memory for the copied object.
    - Copies the actual values from the original object.
    - Each object owns its own separate memory.
    - Changing one object does not affect the other object.
*/

/*
    ============================================================
    1) SHALLOW COPY EXAMPLE
    ============================================================
*/

class ShallowArray {
private:
    int* data;
    int size;

public:
    /*
        Parameterized Constructor

        Syntax:
            ClassName(parameters)

        Purpose:
        - Creates an object using given arguments.
        - Here, it receives the array size and allocates memory.
    */
    ShallowArray(int s) {
        size = s;
        data = new int[size];

        for (int i = 0; i < size; i++) {
            data[i] = 0;
        }
    }

    /*
        Shallow Copy Constructor

        Syntax:
            ClassName(const ClassName& other)

        Purpose:
        - Creates a new object from another existing object.

        Difference:
        - This shallow copy constructor copies only the pointer address.
        - It does NOT allocate a new array.
        - Both objects point to the same memory.
    */
    ShallowArray(const ShallowArray& other) {
        size = other.size;
        data = other.data;
    }

    void setValue(int index, int value) {
        data[index] = value;
    }

    void print() const {
        for (int i = 0; i < size; i++) {
            cout << data[i] << " ";
        }

        cout << endl;
    }

    /*
        Important Note:

        We do not write a destructor here intentionally.

        If we write:
            delete[] data;

        then both objects will try to delete the same memory because
        shallow copy makes them share the same pointer.

        This may cause double delete, which is undefined behavior.
    */
};

/*
    ============================================================
    2) DEEP COPY EXAMPLE
    ============================================================
*/

class DeepArray {
private:
    int* data;
    int size;

public:
    /*
        Default Constructor

        Syntax:
            ClassName()

        Purpose:
        - Creates an object without receiving arguments.
        - Here, it creates an empty array object.
    */
    DeepArray() {
        size = 0;
        data = nullptr;
    }

    /*
        Parameterized Constructor

        Syntax:
            ClassName(parameters)

        Purpose:
        - Creates an object using arguments.
        - Here, it receives the array size and allocates memory.
    */
    DeepArray(int s) {
        size = s;
        data = new int[size];

        for (int i = 0; i < size; i++) {
            data[i] = 0;
        }
    }

    /*
        Deep Copy Constructor

        Syntax:
            ClassName(const ClassName& other)

        Purpose:
        - Creates a new object from another existing object.

        Difference:
        - This deep copy constructor allocates new memory.
        - Then it copies the actual values.
        - Each object has its own independent array.
    */
    DeepArray(const DeepArray& other) {
        size = other.size;
        data = new int[size];

        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
    }

    /*
        Copy Assignment Operator

        Syntax:
            ClassName& operator=(const ClassName& other)

        Purpose:
        - Used when assigning one existing object to another existing object.

        Example:
            DeepArray a(3);
            DeepArray b(5);
            b = a;

        Difference from Copy Constructor:
        - Copy constructor creates a new object.
        - Copy assignment modifies an already existing object.
    */
    DeepArray& operator=(const DeepArray& other) {
        if (this == &other) {
            return *this;
        }

        delete[] data;

        size = other.size;
        data = new int[size];

        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }

        return *this;
    }

    /*
        Move Constructor

        Syntax:
            ClassName(ClassName&& other)

        Purpose:
        - Creates a new object by taking resources from another object.
        - Usually used with temporary objects or std::move.

        Difference from Copy Constructor:
        - Copy constructor duplicates the resource.
        - Move constructor transfers ownership of the resource.
        - It is usually faster for dynamic memory or containers.
    */
    DeepArray(DeepArray&& other) noexcept {
        size = other.size;
        data = other.data;

        other.size = 0;
        other.data = nullptr;
    }

    /*
        Move Assignment Operator

        Syntax:
            ClassName& operator=(ClassName&& other)

        Purpose:
        - Transfers resources from one object to another existing object.

        Difference from Move Constructor:
        - Move constructor creates a new object.
        - Move assignment modifies an already existing object.
    */
    DeepArray& operator=(DeepArray&& other) noexcept {
        if (this == &other) {
            return *this;
        }

        delete[] data;

        size = other.size;
        data = other.data;

        other.size = 0;
        other.data = nullptr;

        return *this;
    }

    /*
        Destructor

        Syntax:
            ~ClassName()

        Purpose:
        - Called automatically when the object is destroyed.
        - Used here to free dynamic memory.

        Important:
        - Destructor is not a constructor.
        - But it is important when the class owns dynamic memory.
    */
    ~DeepArray() {
        delete[] data;
    }

    void setValue(int index, int value) {
        data[index] = value;
    }

    void print() const {
        for (int i = 0; i < size; i++) {
            cout << data[i] << " ";
        }

        cout << endl;
    }
};

/*
    ============================================================
    3) CONSTRUCTORS EXAMPLES IN ONE CLASS
    ============================================================
*/

class Student {
private:
    string name;
    int age;
    const int id;
    int& mark;
    vector<int> grades;

    /*
        This helper function returns a reference to a static integer.

        Why do we need it?
        - The class has a reference data member: int& mark.
        - A reference must always refer to a valid variable.
        - Some constructors do not receive a mark variable.
        - So we use this dummy variable for those constructors.
    */
    static int& dummyMark() {
        static int value = 0;
        return value;
    }

public:
    /*
        Default Constructor

        Syntax:
            ClassName()

        Example:
            Student s;

        Purpose:
        - Creates an object without arguments.

        Difference:
        - It does not receive values from the caller.
        - It uses default values written inside the constructor.
    */
    Student()
        : name("Unknown"),
          age(0),
          id(0),
          mark(dummyMark()) {
        cout << "Default constructor called\n";
    }

    /*
        Parameterized Constructor

        Syntax:
            ClassName(type parameter1, type parameter2, ...)

        Example:
            Student s("Ali", 20, 1001, mark);

        Purpose:
        - Creates an object using values sent by the caller.

        Difference:
        - Unlike the default constructor, it receives arguments.
    */
    Student(string n, int a, int studentId, int& m)
        : name(n),
          age(a),
          id(studentId),
          mark(m) {
        cout << "Parameterized constructor called\n";
    }

    /*
        Constructor With Default Arguments

        Syntax:
            ClassName(type parameter1, type parameter2 = defaultValue)

        Examples:
            Student s1("Mona");
            Student s2("Omar", 22);

        Purpose:
        - Allows calling the same constructor with fewer arguments.

        Difference:
        - If the caller does not provide the second argument,
          the default value is used.
    */
    Student(string n, int a = 18)
        : name(n),
          age(a),
          id(-1),
          mark(dummyMark()) {
        cout << "Constructor with default arguments called\n";
    }

    /*
        initializer_list Constructor

        Syntax:
            ClassName(initializer_list<type> list)

        Example:
            Student s{90, 80, 70};

        Purpose:
        - Allows creating an object using a list of values inside braces.

        Difference:
        - This is useful when the number of input values may vary.
        - It is different from member initializer list.
    */
    Student(initializer_list<int> g)
        : name("Grades Student"),
          age(0),
          id(-2),
          mark(dummyMark()),
          grades(g) {
        cout << "initializer_list constructor called\n";
    }

    /*
        Copy Constructor

        Syntax:
            ClassName(const ClassName& other)

        Examples:
            Student s2 = s1;
            Student s3(s1);

        Purpose:
        - Creates a new object as a copy of an existing object.

        Difference:
        - It is called when a new object is being created from
          another object of the same class.
    */
    Student(const Student& other)
        : name(other.name),
          age(other.age),
          id(other.id),
          mark(other.mark),
          grades(other.grades) {
        cout << "Copy constructor called\n";
    }

    /*
        Move Constructor

        Syntax:
            ClassName(ClassName&& other)

        Example:
            Student s2 = std::move(s1);

        Purpose:
        - Creates a new object by moving resources from another object.

        Difference:
        - It can avoid expensive copying.
        - It is especially useful with strings, vectors, dynamic memory,
          and temporary objects.
    */
    Student(Student&& other) noexcept
        : name(std::move(other.name)),
          age(other.age),
          id(other.id),
          mark(other.mark),
          grades(std::move(other.grades)) {
        cout << "Move constructor called\n";

        other.age = 0;
    }

    /*
        Conversion Constructor

        Syntax:
            ClassName(singleParameter)

        Example:
            Student s = 25;

        Purpose:
        - Allows converting another type into this class type.

        Difference:
        - Because it has one parameter and is not explicit,
          implicit conversion is allowed.
    */
    Student(int a)
        : name("Converted Student"),
          age(a),
          id(-3),
          mark(dummyMark()) {
        cout << "Conversion constructor called\n";
    }

    /*
        Delegating Constructor

        Syntax:
            ClassName(parameters) : ClassName(otherArguments)

        Example:
            Student s('A');

        Purpose:
        - Allows one constructor to call another constructor
          in the same class.

        Difference:
        - Reduces repeated initialization code.
    */
    Student(char gradeLetter)
        : Student(string("Grade ") + gradeLetter, 18) {
        cout << "Delegating constructor called\n";
    }

    /*
        Member Initializer List

        Syntax:
            Constructor(parameters) : member1(value1), member2(value2)

        Purpose:
        - Initializes members before entering the constructor body.

        Difference:
        - This is not a constructor type by itself.
        - It is a constructor syntax.
        - It is required for const members and reference members.
        - It is usually better than assigning values inside the body.
    */

    /*
        Destructor

        Syntax:
            ~ClassName()

        Purpose:
        - Called automatically when an object is destroyed.

        Difference:
        - It is not a constructor.
        - It cleans up instead of creating the object.
    */
    ~Student() {
        cout << "Destructor called for " << name << endl;
    }

    void print() const {
        cout << "Name: " << name << endl;
        cout << "Age: " << age << endl;
        cout << "ID: " << id << endl;
        cout << "Mark: " << mark << endl;

        if (!grades.empty()) {
            cout << "Grades: ";

            for (int grade : grades) {
                cout << grade << " ";
            }

            cout << endl;
        }

        cout << "-------------------------\n";
    }
};

/*
    ============================================================
    4) EXPLICIT CONSTRUCTOR EXAMPLE
    ============================================================
*/

class ExplicitExample {
private:
    int value;

public:
    /*
        Explicit Constructor

        Syntax:
            explicit ClassName(type parameter)

        Examples:
            ExplicitExample e1(10);
            ExplicitExample e2{20};

        Purpose:
        - Prevents implicit conversion.

        Difference from Conversion Constructor:
        - Conversion constructor allows:
              ExplicitExample e = 10;
        - Explicit constructor prevents that.
    */
    explicit ExplicitExample(int v) {
        value = v;
        cout << "Explicit constructor called\n";
    }

    void print() const {
        cout << "Value: " << value << endl;
    }
};

/*
    ============================================================
    5) PRIVATE CONSTRUCTOR EXAMPLE
    ============================================================
*/

class Logger {
private:
    /*
        Private Constructor

        Syntax:
            private:
                ClassName()

        Purpose:
        - Prevents creating objects directly from outside the class.

        Difference:
        - Normal public constructors can be called from outside.
        - Private constructors can only be called from inside the class.
    */
    Logger() {
        cout << "Private constructor called\n";
    }

public:
    /*
        Static function used to access the single Logger object.

        This is a simple Singleton-style example.
    */
    static Logger& getInstance() {
        static Logger instance;
        return instance;
    }

    void log(const string& message) {
        cout << "LOG: " << message << endl;
    }
};

/*
    ============================================================
    6) DEFAULTED AND DELETED CONSTRUCTORS EXAMPLE
    ============================================================
*/

class ConstructorControl {
private:
    int value = 0;

public:
    /*
        Defaulted Constructor

        Syntax:
            ClassName() = default;

        Purpose:
        - Asks the compiler to generate the default constructor.

        Difference:
        - Useful when you want compiler-generated behavior explicitly.
    */
    ConstructorControl() = default;

    /*
        Parameterized Constructor
    */
    ConstructorControl(int v) {
        value = v;
        cout << "ConstructorControl parameterized constructor called\n";
    }

    /*
        Deleted Copy Constructor

        Syntax:
            ClassName(const ClassName& other) = delete;

        Purpose:
        - Prevents copying objects of this class.

        Difference:
        - If someone tries to copy, the code will not compile.
    */
    ConstructorControl(const ConstructorControl& other) = delete;

    void print() const {
        cout << "Value: " << value << endl;
    }
};

/*
    ============================================================
    7) CONSTEXPR CONSTRUCTOR EXAMPLE
    ============================================================
*/

class Point {
private:
    int x;
    int y;

public:
    /*
        constexpr Constructor

        Syntax:
            constexpr ClassName(parameters)

        Purpose:
        - Allows creating objects that can be evaluated at compile time.

        Difference:
        - Useful for constant objects and compile-time calculations.
    */
    constexpr Point(int xValue, int yValue)
        : x(xValue),
          y(yValue) {
    }

    constexpr int getX() const {
        return x;
    }

    constexpr int getY() const {
        return y;
    }
};

/*
    ============================================================
    MAIN FUNCTION
    ============================================================
*/

int main() {
    cout << "================ SHALLOW COPY ================\n";

    ShallowArray shallow1(3);

    shallow1.setValue(0, 10);
    shallow1.setValue(1, 20);
    shallow1.setValue(2, 30);

    /*
        This calls the shallow copy constructor.

        Both shallow1 and shallow2 will share the same array.
    */
    ShallowArray shallow2 = shallow1;

    cout << "shallow1 before change: ";
    shallow1.print();

    cout << "shallow2 before change: ";
    shallow2.print();

    shallow2.setValue(0, 999);

    cout << "shallow1 after changing shallow2: ";
    shallow1.print();

    cout << "shallow2 after changing shallow2: ";
    shallow2.print();

    cout << "\n";

    cout << "================ DEEP COPY ================\n";

    DeepArray deep1(3);

    deep1.setValue(0, 10);
    deep1.setValue(1, 20);
    deep1.setValue(2, 30);

    /*
        This calls the deep copy constructor.

        deep1 and deep2 will have separate arrays.
    */
    DeepArray deep2 = deep1;

    cout << "deep1 before change: ";
    deep1.print();

    cout << "deep2 before change: ";
    deep2.print();

    deep2.setValue(0, 999);

    cout << "deep1 after changing deep2: ";
    deep1.print();

    cout << "deep2 after changing deep2: ";
    deep2.print();

    cout << "\n";

    cout << "================ COPY ASSIGNMENT ================\n";

    DeepArray deep3(2);

    deep3.setValue(0, 7);
    deep3.setValue(1, 8);

    cout << "deep3 before assignment: ";
    deep3.print();

    /*
        This calls the copy assignment operator, not the copy constructor.

        deep3 already exists, so assignment is used.
    */
    deep3 = deep2;

    cout << "deep3 after assignment from deep2: ";
    deep3.print();

    cout << "\n";

    cout << "================ MOVE CONSTRUCTOR ================\n";

    /*
        This calls the move constructor.

        deep3 gives its internal array to deep4.
    */
    DeepArray deep4 = std::move(deep3);

    cout << "deep4 after move: ";
    deep4.print();

    cout << "\n";

    cout << "================ MOVE ASSIGNMENT ================\n";

    DeepArray deep5(1);
    deep5.setValue(0, 555);

    /*
        This calls the move assignment operator.

        deep5 already exists, so move assignment is used.
    */
    deep5 = std::move(deep4);

    cout << "deep5 after move assignment: ";
    deep5.print();

    cout << "\n";

    cout << "================ STUDENT CONSTRUCTORS ================\n";

    int mark = 95;

    /*
        Default constructor
    */
    Student s1;
    s1.print();

    /*
        Parameterized constructor
    */
    Student s2("Ali", 20, 1001, mark);
    s2.print();

    /*
        Constructor with default arguments

        The age is not passed, so it uses the default value 18.
    */
    Student s3("Mona");
    s3.print();

    /*
        Constructor with default arguments

        The age is passed explicitly.
    */
    Student s4("Omar", 22);
    s4.print();

    /*
        initializer_list constructor
    */
    Student s5{90, 80, 70};
    s5.print();

    /*
        Copy constructor
    */
    Student s6 = s2;
    s6.print();

    /*
        Move constructor
    */
    Student s7 = std::move(s3);
    s7.print();

    /*
        Conversion constructor

        The integer 25 is converted into a Student object.
    */
    Student s8 = 25;
    s8.print();

    /*
        Delegating constructor

        The char constructor calls another constructor.
    */
    Student s9('A');
    s9.print();

    cout << "\n";

    cout << "================ EXPLICIT CONSTRUCTOR ================\n";

    /*
        Direct initialization is allowed.
    */
    ExplicitExample e1(10);
    e1.print();

    /*
        Brace initialization is allowed.
    */
    ExplicitExample e2{20};
    e2.print();

    /*
        Copy initialization is not allowed because the constructor is explicit.

        This line would cause a compilation error:

            ExplicitExample e3 = 30;
    */

    cout << "\n";

    cout << "================ PRIVATE CONSTRUCTOR ================\n";

    /*
        Direct object creation is not allowed because the constructor is private.

        This line would cause a compilation error:

            Logger logger;
    */

    Logger& logger = Logger::getInstance();
    logger.log("This object was created using a private constructor.");

    cout << "\n";

    cout << "================ DEFAULTED AND DELETED CONSTRUCTORS ================\n";

    /*
        Defaulted constructor
    */
    ConstructorControl c1;
    c1.print();

    /*
        Parameterized constructor
    */
    ConstructorControl c2(50);
    c2.print();

    /*
        Copying is not allowed because the copy constructor is deleted.

        This line would cause a compilation error:

            ConstructorControl c3 = c2;
    */

    cout << "\n";

    cout << "================ CONSTEXPR CONSTRUCTOR ================\n";

    /*
        This object can be created at compile time.
    */
    constexpr Point p(3, 4);

    cout << "Point x = " << p.getX() << endl;
    cout << "Point y = " << p.getY() << endl;

    cout << "\n";

    return 0;
}