#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <string.h>

#define SHM_NAME "/bank_shm"
#define SEM_MUTEX_NAME "/bank_sem_mutex"
#define SEM_EMI_NAME "/bank_sem_emi"
#define SEM_WITHDRAW_NAME "/bank_sem_withdraw"
#define SEM_DEPOSIT_NAME "/bank_sem_deposit"

// Shared memory structure
typedef struct {
    float balance;
    sem_t mutex;
    sem_t emi;
    sem_t withdraw;
    sem_t deposit;
} BankData;

// Deposit process
void deposit_process(BankData* bank_data) {
   int i=0,dept_balance=0,flag=1; 
	printf("Enter deposit balance:");
	scanf("%d",&dept_balance);
    for ( i = 0; i < 5; ++i) {
        sem_wait(&bank_data->mutex);
	if(flag==1){        
	bank_data->balance += dept_balance;
	flag+=1; 
        printf("\n\nDeposited $%d. New balance: $%.2f\n",dept_balance, bank_data->balance);
	}        

	    sem_post(&bank_data->mutex);
        sem_post(&bank_data->emi);
        sem_post(&bank_data->withdraw);
	dept_balance=0;
        sleep(3); 
    }
    exit(0);
}

// EMI process
void emi_process(BankData* bank_data) {
	int dept_balance=0;
	char *dep_yes_no;
    while (1) {
        sem_wait(&bank_data->emi);
        sem_wait(&bank_data->mutex);
        if (bank_data->balance >= 50) {
            bank_data->balance -= 50;
            printf("\n\nEMI of $50 paid. New balance: $%.2f\n", bank_data->balance);
        } else {
            printf("EMI process: Insufficient balance, waiting...\n");
            sem_post(&bank_data->emi);
        
        }
        sem_post(&bank_data->mutex);
        sleep(1);
    }
}

// Withdraw process
void withdraw_process(BankData* bank_data) {
	int with_ammount=0;
    while (1) {
        sem_wait(&bank_data->withdraw);
        sem_wait(&bank_data->mutex);
	printf("Enter the withdrawal ammount:");
	scanf("%d",&with_ammount);
        if (bank_data->balance >= with_ammount) {
            bank_data->balance -= with_ammount;
            printf("\n\nWithdrawn %d. New balance: $%.2f\n",with_ammount, bank_data->balance);
        } else {
            printf("\n\nWithdraw process: Insufficient balance, waiting...\n");
            sem_post(&bank_data->withdraw);
        }
        sem_post(&bank_data->mutex);
        sleep(2);
    }
}

int main() {
    int shm_fd;
    BankData* bank_data;

    shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, sizeof(BankData));
    bank_data = mmap(NULL, sizeof(BankData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    bank_data->balance = 0;
    sem_init(&bank_data->mutex, 1, 1);
    sem_init(&bank_data->emi, 1, 0);
    sem_init(&bank_data->withdraw, 1, 0);
    sem_init(&bank_data->deposit, 1, 1);

    pid_t deposit_pid = fork();
    if (deposit_pid == 0) {
        deposit_process(bank_data);
    }

    pid_t emi_pid = fork();
    if (emi_pid == 0) {
        emi_process(bank_data);
    }

    pid_t withdraw_pid = fork();
    if (withdraw_pid == 0) {
        withdraw_process(bank_data);
    }

    wait(NULL);
    wait(NULL);
    wait(NULL);

    
    sem_destroy(&bank_data->mutex);
    sem_destroy(&bank_data->emi);
    sem_destroy(&bank_data->withdraw);
    sem_destroy(&bank_data->deposit);
    munmap(bank_data, sizeof(BankData));
    shm_unlink(SHM_NAME);

    return 0;
}
