#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define NUM_ACCOUNTS 5
#define NUM_TRANSFERS 5

// Monetary value in cents
typedef long currency;

void sprintf_currency(char *buffer, currency c) {
  if (c < 0) {
    c = -c;
    sprintf(buffer, "-");
    buffer += sizeof(char);
  }
  sprintf(buffer, "$%ld.%.2ld", c / 100, c % 100);
}

void printf_currency(currency c) {
  if (c < 0) {
    c = -c;
    printf("-");
  }
  printf("$%ld.%.2ld", c / 100, c % 100);
}

// Accounts (shared resource)
typedef struct account {
  int account_id;
  currency balance;
  int transaction_count;
  pthread_mutex_t lock; // Account access mutex
} account;

// Global account array
account accounts[NUM_ACCOUNTS];

// Threads

pthread_t transfers[NUM_TRANSFERS];

typedef struct transfer_args {
  int transfer_id;
  account *from;
  account *to;
  currency amount;
} transfer_args;

void *transfer_thread(void *arg) {
  transfer_args *args = (transfer_args *)arg;
  int id = args->transfer_id;
  account *from = args->from;
  account *to = args->to;
  currency amount = args->amount;

  pthread_mutex_lock(&from->lock);
  printf("Thread %u: Locked account %d\n", id, from->account_id);

  usleep(100); // Delay to account for other threads

  pthread_mutex_lock(&to->lock);
  printf("Thread %u: Locked account %d\n", id, to->account_id);

  from->balance -= amount;
  to->balance += amount;
  from->transaction_count++;
  to->transaction_count++;

  printf("Thread %u: Unlocked account %u\n", id, from->account_id);
  printf("Thread %u: Unlocked account %u\n", id, to->account_id);
  pthread_mutex_unlock(&to->lock);
  pthread_mutex_unlock(&from->lock);

  return NULL;
}

int main() {
  // Initialize all accounts with 0 balance
  for (int i = 0; i < NUM_ACCOUNTS; i++) {
    accounts[i] = (account){.account_id = i, .balance = 0};
    pthread_mutex_init(&accounts[i].lock, NULL);
  }

  // Initialize transfers
  transfer_args args[NUM_TRANSFERS];
  for (int i = 0; i < NUM_TRANSFERS; i++) {
    args[i] = (transfer_args){.transfer_id = i,
                              .from = &accounts[i % NUM_ACCOUNTS],
                              .to = &accounts[(i + 1) % NUM_ACCOUNTS],
                              .amount = 1000};
    pthread_create(&transfers[i], NULL, transfer_thread, &args[i]);
  }

  // Wait for all threads to finish
  for (int i = 0; i < NUM_TRANSFERS; i++) {
    pthread_join(transfers[i], NULL);
  }

  return 0;
}
