#include <stdio.h>

/* Function: sizeOfPointerVariable
 * Description: This function declares variables of different types (int, float, char),
 * initializes pointers to those variables, and prints the size of the pointer variables.
*/
void sizeOfPointerVariable()
{
    int x = 10;        // Integer variable
    float y = 1.3f;    // Float variable
    char z = 'p';      // Char variable

    // Declaring pointers and assigning addresses of variables
    int *ptr_x = &x;
    float *ptr_y = &y;
    char *ptr_z = &z;

    // Printing the size of each pointer variable
    printf("Size of integer: %lu\n", sizeof(x));
    printf("Size of float: %lu\n", sizeof(y));
    printf("Size of char: %lu\n", sizeof(z));

    // Printing the size of each pointer variable
    printf("Size of integer pointer: %lu\n", sizeof(ptr_x));
    printf("Size of float pointer: %lu\n", sizeof(ptr_y));
    printf("Size of char pointer: %lu\n", sizeof(ptr_z));
}

/* Function: definePointer
 * Description: Demonstrates pointer declaration, initialization, and usage.
 * It also shows how pointers can be used to access and modify variable values.
*/
void definePointer()
{
    int x = 10;        // Integer variable

    // Declaring a pointer to an integer and assigning it the address of x
    int *ptr;
    ptr = &x;
    // Dereferencing the pointer to access the value of x
    printf("Value of x = %d\n", *ptr);

    // Modifying the value of x through the pointer
    *ptr = 20;

    // Printing the updated value of x and pointer-related details
    printf("(ptr) Value of x = %d\n", *ptr);
    printf("x: %d \nAddress of x: %p \n\n", x, (void*)&x);
    printf("ptr: %p \nAddress of ptr: %p \n\n", (void*)ptr, (void*)&ptr);
}

/* Function: pointerToPointer
 * Description: Demonstrates the concept of a pointer to a pointer (double pointer).
 * Shows how double pointers work with multi-level dereferencing.
*/
void pointerToPointer()
{
    int var = 10;        // Integer variable
    int *intptr = &var;  // Pointer to an integer
    int **ptrptr = &intptr; // Pointer to a pointer

    // Printing addresses and values using single and double pointers
    printf("var: %d \nAddress of var: %p \n\n", var, (void*)&var);
    printf("intptr: %p \nAddress of intptr: %p \n\n", (void*)intptr, (void*)&intptr);
    printf("var: %d \nValue at intptr: %d \n\n", var, *intptr);
    printf("ptrptr: %p \nAddress of ptrptr: %p \n\n", (void*)ptrptr, (void*)&ptrptr);
    printf("intptr: %p \nValue at ptrptr: %p \n\n", (void*)intptr, (void*)*ptrptr);
    printf("var: %d \n*intptr: %d \n**ptrptr: %d\n", var, *intptr, **ptrptr);
}

/* Function: incrementOperatorPointer
 * Description: Demonstrates pointer arithmetic, specifically how the address
 * stored in a pointer changes when it is incremented.
*/
void incrementOperatorPointer()
{
    int a = 5;       // Integer variable
    int *b = &a;     // Pointer to integer

    // Printing the address of a and showing how it changes after incrementing the pointer
    printf("Address of a: %p\n", (void*)b);
    b += 2;  // Increment the pointer (moves to the next memory address based on data type size)
    printf("After increment, Address of a: %p\n", (void*)b);
}

/* Function: traversingArrayUsingPointer
 * Description: Demonstrates how to traverse an array using a pointer.
 * The pointer is incremented to move through the array.
*/
void traversingArrayUsingPointer()
{
    int arr[5] = {1, 4, 3, 8, 0};  // Array of integers
    int *b = arr;                  // Pointer to the first element of the array


    printf("Address of arr[0]: %p, value at arr[0]: %d\n", (void*)arr, *arr);
    // printf("Address of arr[0]: %p, value at arr[0]: %d\n", (void*)arr, arr);//arr is address
    // arr++;
    // printf("Address of arr[1]: %p, value at arr[1]: %d\n", (void*)arr+1, *arr+1); //undefined behaviour
    printf("Address of arr[1]: %p, value at arr[1]: %d\n", (void*)(arr+1), *(arr+1));


    // Printing the address and value of each array element using pointer arithmetic
    printf("Address of arr[0]: %p, value at arr[0]: %d\n", (void*)b, *b);

    b++;
    printf("Address of arr[1]: %p, value at arr[1]: %d\n", (void*)b, *b);

    b++;
    printf("Address of arr[2]: %p, value at arr[2]: %d\n", (void*)b, *b);

    b++;
    printf("Address of arr[3]: %p, value at arr[3]: %d\n",(void*) b, *b);

    b++;
    printf("Address of arr[4]: %p, value at arr[4]: %d\n", (void*)b, *b);
}

/* Function: traversingArrayUsingDereference
 * Description: Demonstrates array traversal by dereferencing a pointer.
 * It shows two approaches: pointer arithmetic and pointer incrementation within the loop.
*/
void traversingArrayUsingDereference()
{
    int arr[5] = {1, 2, 3, 4, 5};  // Array of integers
    int *b = arr;                  // Pointer to the first element of the array
    int i;
    int len = sizeof(arr)/sizeof(int);  // Calculating the length of the array

    // Loop 1: Using pointer arithmetic to traverse the array
    for(i = 0; i < len; i++)
    {
        printf("arr[%d] = %d\n", i, *(b + i));
    }
    printf("value of b after loop 1:%d\n",*b);

    // Loop 2: Incrementing the pointer in each iteration
    printf("\nUsing pointer incrementation:\n");
    for(i = 0; i < len; i++)
    {
        printf("arr[%d] = %d\n", i, *b);
        b++;
    }
    // printf("value of b after loop 2:%d\n",*b); //undefined behaviour
    printf("value of b-1 after loop 2:%d\n",*(b-1));

    // Loop 3: Using a pointer variable directly in the loop condition
    printf("\nUsing pointer in loop condition:\n");
    for(int *ip = arr; ip < arr + len; ip++)
    {
        printf("arr[%ld] = %d\n", ip - arr, *ip);
    }
}

void swap( int a, int b )
{
    int temp = a;
    a = b;
    b = temp;
}


void swapUsingPointer( int *a, int *b )
{
   int temp = *a;
   *a = *b;
   *b = temp;
}
