#define _GNU_SOURCE

#include "lab2.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#define CACHE_SIZE 1024  // Количество блоков в кэше
#define BLOCK_SIZE 4096  // Размер блока в байтах
#define MAX_OPEN_FILES 256

// Структура для идентификации файла
typedef struct FileKey {
  dev_t dev;    // Идентификатор устройства
  ino_t inode;  // Идентификатор inode
} FileKey;

// Структура блока кэша
typedef struct CacheBlock {
  FileKey file_key;    // Ключ файла
  off_t block_offset;  // Смещение блока в файле
  int frequency;       // Частота использования
  int is_dirty;        // Флаг модификации
  void* data;          // Указатель на данные блока
} CacheBlock;

// Метаданные файла
typedef struct FileMetadata {
  FileKey file_key;  // Ключ файла
  off_t offset;      // Текущая позиция в файле
  int fd;            // Файловый дескриптор
} FileMetadata;

// Глобальные переменные
CacheBlock cache[CACHE_SIZE];
int cache_count = 0;
FileMetadata file_metadata[MAX_OPEN_FILES] = {0};
pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;

// Утилита для выравнивания буфера
void* aligned_alloc(size_t alignment, size_t size) {
  void* ptr;
  if (posix_memalign(&ptr, alignment, size) != 0) {
    return NULL;
  }
  return ptr;
}

// Генерация ключа файла
int get_file_key(int fd, FileKey* key) {
  struct stat st;
  if (fstat(fd, &st) < 0) {
    perror("get_file_key: failed to stat file");
    return -1;
  }
  key->dev = st.st_dev;
  key->inode = st.st_ino;
  return 0;
}

// Регистрация файлового дескриптора
int register_fd(int fd) {
  FileKey key;
  if (get_file_key(fd, &key) < 0) {
    return -1;
  }
  for (int i = 0; i < MAX_OPEN_FILES; i++) {
    if (file_metadata[i].file_key.inode == 0) {
      file_metadata[i].file_key = key;
      file_metadata[i].offset = 0;
      file_metadata[i].fd = fd;
      return 0;
    }
  }
  errno = EMFILE;
  return -1;
}

// Удаление дескриптора
int unregister_fd(int fd) {
  for (int i = 0; i < MAX_OPEN_FILES; i++) {
    if (file_metadata[i].fd == fd) {
      memset(&file_metadata[i], 0, sizeof(FileMetadata));
      return 0;
    }
  }
  errno = EBADF;
  return -1;
}

// Поиск блока в кэше
int find_block_in_cache(FileKey* key, off_t block_offset) {
  for (int i = 0; i < CACHE_SIZE; i++) {
    if (cache[i].file_key.inode == key->inode && cache[i].file_key.dev == key->dev &&
        cache[i].block_offset == block_offset) {
      return i;
    }
  }
  return -1;
}

// Вытеснение блока
int evict_block() {
  int lfu_index = -1;
  int min_frequency = INT32_MAX;

  for (int i = 0; i < CACHE_SIZE; i++) {
    if (cache[i].frequency < min_frequency) {
      min_frequency = cache[i].frequency;
      lfu_index = i;
    }
  }

  if (cache[lfu_index].is_dirty) {
    int fd = -1;
    for (int i = 0; i < MAX_OPEN_FILES; i++) {
      if (file_metadata[i].file_key.inode == cache[lfu_index].file_key.inode &&
          file_metadata[i].file_key.dev == cache[lfu_index].file_key.dev) {
        fd = file_metadata[i].fd;
        break;
      }
    }

    if (fd < 0 ||
        pwrite(fd, cache[lfu_index].data, BLOCK_SIZE, cache[lfu_index].block_offset) < 0) {
      perror("evict_block: failed to write block to disk");
      return -1;
    }
  }

  free(cache[lfu_index].data);
  memset(&cache[lfu_index], 0, sizeof(CacheBlock));
  return lfu_index;
}

// Загрузка блока в кэш
int load_block_into_cache(FileKey* key, off_t block_offset) {
  int cache_index;

  if (cache_count < CACHE_SIZE) {
    cache_index = cache_count++;
  } else {
    cache_index = evict_block();
    if (cache_index < 0) {
      return -1;
    }
  }

  cache[cache_index].data = aligned_alloc(BLOCK_SIZE, BLOCK_SIZE);
  if (!cache[cache_index].data) {
    perror("load_block_into_cache: failed to allocate aligned memory");
    return -1;
  }

  cache[cache_index].file_key = *key;
  cache[cache_index].block_offset = block_offset;
  cache[cache_index].frequency = 1;
  cache[cache_index].is_dirty = 0;

  int fd = -1;
  for (int i = 0; i < MAX_OPEN_FILES; i++) {
    if (file_metadata[i].file_key.inode == key->inode &&
        file_metadata[i].file_key.dev == key->dev) {
      fd = file_metadata[i].fd;
      break;
    }
  }

  if (fd < 0 || pread(fd, cache[cache_index].data, BLOCK_SIZE, block_offset) < 0) {
    perror("load_block_into_cache: failed to read block from disk");
    free(cache[cache_index].data);
    return -1;
  }

  return cache_index;
}

// Сброс измененных данных на диск
int flush_dirty_blocks(FileKey* key) {
  for (int i = 0; i < CACHE_SIZE; i++) {
    if (cache[i].file_key.inode == key->inode && cache[i].file_key.dev == key->dev &&
        cache[i].is_dirty) {
      int fd = -1;
      for (int j = 0; j < MAX_OPEN_FILES; j++) {
        if (file_metadata[j].file_key.inode == key->inode &&
            file_metadata[j].file_key.dev == key->dev) {
          fd = file_metadata[j].fd;
          break;
        }
      }

      if (fd < 0 || pwrite(fd, cache[i].data, BLOCK_SIZE, cache[i].block_offset) < 0) {
        perror("flush_dirty_blocks: failed to write block to disk");
        return -1;
      }
      cache[i].is_dirty = 0;
    }
  }
  return 0;
}

// Функция чтения из файла
ssize_t lab2_read(int fd, void* buf, size_t count, off_t offset) {
  pthread_mutex_lock(&cache_lock);

  FileKey key;
  if (get_file_key(fd, &key) < 0) {
    pthread_mutex_unlock(&cache_lock);
    return -1;
  }

  size_t bytes_read = 0;
  while (count > 0) {
    off_t block_offset = (offset / BLOCK_SIZE) * BLOCK_SIZE;
    size_t block_start = offset % BLOCK_SIZE;
    size_t to_read = BLOCK_SIZE - block_start;
    if (to_read > count)
      to_read = count;

    int cache_index = find_block_in_cache(&key, block_offset);
    if (cache_index < 0) {
      cache_index = load_block_into_cache(&key, block_offset);
      if (cache_index < 0) {
        pthread_mutex_unlock(&cache_lock);
        return -1;
      }
    }

    memcpy(buf + bytes_read, cache[cache_index].data + block_start, to_read);
    cache[cache_index].frequency++;

    bytes_read += to_read;
    count -= to_read;
    offset += to_read;
  }

  pthread_mutex_unlock(&cache_lock);
  return bytes_read;
}

// Функция записи в файл
ssize_t lab2_write(int fd, const void* buf, size_t count, off_t offset) {
  pthread_mutex_lock(&cache_lock);

  FileKey key;
  if (get_file_key(fd, &key) < 0) {
    pthread_mutex_unlock(&cache_lock);
    return -1;
  }

  size_t bytes_written = 0;
  while (count > 0) {
    off_t block_offset = (offset / BLOCK_SIZE) * BLOCK_SIZE;
    size_t block_start = offset % BLOCK_SIZE;
    size_t to_write = BLOCK_SIZE - block_start;
    if (to_write > count)
      to_write = count;

    int cache_index = find_block_in_cache(&key, block_offset);
    if (cache_index < 0) {
      cache_index = load_block_into_cache(&key, block_offset);
      if (cache_index < 0) {
        pthread_mutex_unlock(&cache_lock);
        return -1;
      }
    }

    memcpy(cache[cache_index].data + block_start, buf + bytes_written, to_write);
    cache[cache_index].frequency++;
    cache[cache_index].is_dirty = 1;

    bytes_written += to_write;
    count -= to_write;
    offset += to_write;
  }

  pthread_mutex_unlock(&cache_lock);
  return bytes_written;
}

// Реализация API
int lab2_open(const char* path, int flags) {
  int fd = open(path, flags | O_DIRECT);
  if (fd < 0) {
    perror("lab2_open: failed to open file");
    return -1;
  }
  if (register_fd(fd) < 0) {
    close(fd);
    return -1;
  }
  return fd;
}

int lab2_close(int fd) {
  pthread_mutex_lock(&cache_lock);

  FileKey key;
  if (get_file_key(fd, &key) < 0) {
    pthread_mutex_unlock(&cache_lock);
    return -1;
  }

  if (flush_dirty_blocks(&key) < 0) {
    pthread_mutex_unlock(&cache_lock);
    return -1;
  }

  for (int i = 0; i < CACHE_SIZE; i++) {
    if (cache[i].file_key.inode == key.inode && cache[i].file_key.dev == key.dev) {
      free(cache[i].data);
      memset(&cache[i], 0, sizeof(CacheBlock));
    }
  }

  unregister_fd(fd);
  pthread_mutex_unlock(&cache_lock);

  if (close(fd) < 0) {
    perror("lab2_close: failed to close file");
    return -1;
  }
  return 0;
}

// Функция lseek
off_t lab2_lseek(int fd, off_t offset, int whence) {
  pthread_mutex_lock(&cache_lock);

  FileKey key;
  if (get_file_key(fd, &key) < 0) {
    pthread_mutex_unlock(&cache_lock);
    return -1;
  }

  for (int i = 0; i < MAX_OPEN_FILES; i++) {
    if (file_metadata[i].fd == fd) {
      off_t new_offset;
      switch (whence) {
        case SEEK_SET:
          new_offset = offset;
          break;
        case SEEK_CUR:
          new_offset = file_metadata[i].offset + offset;
          break;
        case SEEK_END: {
          struct stat st;
          if (fstat(fd, &st) < 0) {
            pthread_mutex_unlock(&cache_lock);
            return -1;
          }
          new_offset = st.st_size + offset;
          break;
        }
        default:
          errno = EINVAL;
          pthread_mutex_unlock(&cache_lock);
          return -1;
      }

      if (new_offset < 0) {
        errno = EINVAL;
        pthread_mutex_unlock(&cache_lock);
        return -1;
      }

      file_metadata[i].offset = new_offset;
      pthread_mutex_unlock(&cache_lock);
      return new_offset;
    }
  }

  errno = EBADF;
  pthread_mutex_unlock(&cache_lock);
  return -1;
}

// Функция fsync
int lab2_fsync(int fd) {
  pthread_mutex_lock(&cache_lock);

  FileKey key;
  if (get_file_key(fd, &key) < 0) {
    pthread_mutex_unlock(&cache_lock);
    return -1;
  }

  int result = flush_dirty_blocks(&key);

  pthread_mutex_unlock(&cache_lock);
  return result;
}

Нужно 1. Сдлеать так чтобы можно было быстро искать FileMetadata по fd
2. чтобы lab2_read и lab2_write пеестали использовать внешний offset и стали использовать offset из FileMetadata
3. Переписать так чтобы 1. lab2_read и lab2_write возвращали реальное количество байт (т.е. ответ pread) т.е. основная проблема сейчас это то что независимо от того, что возвращает pread в load_block_into_cache мы продолжаем считать что  bytes_read += to_read, хотя по факту ответ от pread мог быть 0 (ну тоесть обработать 0 ответ от pread