#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <unistd.h>

#define bool int
#define true 1
#define false 0

#define MAX_LINE_LENGTH 256
#define GB (1024 * 1024 * 1024)
#define MB (1024 * 1024)

typedef enum {
  RG_ALL,
  RG_PROGRAM_ONLY,
  RG_ANONYMOUS_ONLY,
  RG_ANONYMOUS_PROGRAM
} RG_MODE;

typedef struct {
  void *start_addr;
  void *end_addr;
  size_t size;
  char permissions[5];
  unsigned long offset;
  char device[8];
  unsigned long inode;
  char pathname[256];
} mem_rg;

void get_sys_mem_info() {
  printf("\n=== System Memory Information ===\n");

  struct sysinfo inf;
  sysinfo(&inf);

  printf("Total RAM:\t%5.2f GB\n", (float)(inf.totalram * inf.mem_unit) / GB);
  printf("Free RAM:\t%5.2f GB\n", (float)(inf.freeram * inf.mem_unit) / GB);
  printf("Shared RAM:\t%5.2f GB\n", (float)(inf.sharedram * inf.mem_unit) / GB);
  printf("Buffer RAM:\t%5.2f GB\n", (float)(inf.bufferram * inf.mem_unit) / GB);
  printf("Total Swap:\t%5.2f GB\n", (float)(inf.totalswap * inf.mem_unit) / GB);
  printf("Free Swap:\t%5.2f GB\n", (float)(inf.freeswap * inf.mem_unit) / GB);
  printf("Number of processes: %d\n", inf.procs);

  printf("\n=== Additional Memory Info (/proc/meminfo) ===\n");

  FILE *proc_meminfo = fopen("/proc/meminfo", "r");

  char line[MAX_LINE_LENGTH];
  while (fgets(line, MAX_LINE_LENGTH, proc_meminfo) != NULL) {
    if (strstr(line, "MemTotal:") || strstr(line, "MemFree:") ||
        strstr(line, "MemAvailable:") || strstr(line, "Buffers:") ||
        strstr(line, "Cached:") || strstr(line, "SwapTotal:") ||
        strstr(line, "SwapFree:") || strstr(line, "Dirty:") ||
        strstr(line, "Writeback:") || strstr(line, "Shmem:")) {
      printf("%s", line);
    }
  }

  fclose(proc_meminfo);

  printf("\n=== Memory Pages Information ===\n");

  long p_size = sysconf(_SC_PAGESIZE);
  long p_phys = sysconf(_SC_PHYS_PAGES);
  long p_avai = sysconf(_SC_AVPHYS_PAGES);

  printf("Page Size:             %8ld bytes\n", p_size);
  printf("Total Phys Pages:      %8ld\n", p_phys);
  printf("Available Phys Pages:  %8ld\n", p_avai);
  printf("Total Phys Memory:     %8.2f GB\n", (float)(p_phys * p_size) / GB);
  printf("Available Phys Memory: %8.2f GB\n", (float)(p_avai * p_size) / GB);
}

mem_rg parse_rg(char *line) {
  mem_rg region;
  memset(&region, 0, sizeof(region));

  sscanf(line, "%p-%p %4s %lx %7s %lu %255[^\n]", &region.start_addr,
         &region.end_addr, region.permissions, &region.offset, region.device,
         &region.inode, region.pathname);

  region.size = (size_t)region.end_addr - (size_t)region.start_addr;

  return region;
}

int is_valid_rg(mem_rg *region, RG_MODE mode) {
  switch (mode) {
  case RG_PROGRAM_ONLY:
    return strstr(region->pathname, "info") != NULL;

  case RG_ANONYMOUS_ONLY:
    return strstr(region->pathname, "[heap]") != NULL ||
           strstr(region->pathname, "[stack]") != NULL ||
           region->pathname[0] == '\0';

  case RG_ANONYMOUS_PROGRAM:
    return strstr(region->pathname, "info") != NULL ||
           strstr(region->pathname, "[heap]") != NULL ||
           strstr(region->pathname, "[stack]") != NULL ||
           region->pathname[0] == '\0';

  case RG_ALL:
  default:
    return 1;
  }
}

void get_prog_addr(FILE *fp) {
  printf("\n=== Program Memory Addresses ===\n");

  rewind(fp);

  char line[MAX_LINE_LENGTH];
  fgets(line, MAX_LINE_LENGTH, fp);

  mem_rg start_region = parse_rg(line);
  mem_rg end_region = {0};

  while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
    mem_rg curr_region = parse_rg(line);

    if (is_valid_rg(&curr_region, RG_PROGRAM_ONLY) &&
        curr_region.end_addr > end_region.end_addr)
      end_region = curr_region;
  }

  printf("Program Start Address: %p\n", start_region.start_addr);
  printf("Program End Address:   %p\n", end_region.end_addr);

  printf("\n================================\n");
}

void list_rg(FILE *fp) {
  rewind(fp);

  RG_MODE mode = RG_ALL;

  char line[MAX_LINE_LENGTH];
  int included_count = 0;
  int total_count = 0;

  while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
    total_count++;

    mem_rg region = parse_rg(line);

    if (!is_valid_rg(&region, mode))
      continue;

    included_count++;

    printf("--- Memory Region №%2d ---\n", included_count);
    printf("Start Address: %p\n", region.start_addr);
    printf("End Address:   %p\n", region.end_addr);
    printf("Size:          %zu bytes (%.2f MB)\n", region.size,
           (float)region.size / MB);
    printf("Permissions:   %s\n", region.permissions);
    printf("Offset:        0x%lx\n", region.offset);
    printf("Device:        %s\n", region.device);
    printf("Inode:         %lu\n", region.inode);
    printf("Pathname:      %s\n",
           region.pathname[0] ? region.pathname : "[anonymous]");
    printf("-------------------------\n");
  }

  printf("Blocks Total:  %d\n", total_count);
  printf("Blocks Shown:  %d\n", included_count);
}

void alloc_cmp(FILE *fp) {
  printf("\n=== Allocating Memory ===\n");

  printf("Before allocation:\n");
  list_rg(fp);

  size_t allocation_size = 10 * MB;
  printf("\nAllocating %zu bytes (%.2f MB) of memory...\n", allocation_size,
         (float)allocation_size / MB);

  void *memory = mmap(NULL, allocation_size, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

  printf("Memory is allocated at %p\n", memory);

  printf("\nAfter allocation:\n");
  list_rg(fp);

  if (munmap(memory, allocation_size) == 0) {
    printf("\nMemory freed.\n");
  } else {
    perror("munmap");
  }
}

int main(int argc, char *argv[]) {
  get_sys_mem_info();

  FILE *fp = fopen("/proc/self/maps", "r");
  get_prog_addr(fp);

  alloc_cmp(fp);

  fclose(fp);

  return EXIT_SUCCESS;
}
