#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "mavka/biblioteka.h"
#include "mavka/prystriy.h"

р64 бібліотека_мавки_синус_р64(р64 значення) {
  return sin(значення);
}

р64 бібліотека_мавки_косинус_р64(р64 значення) {
  return cos(значення);
}

р64 бібліотека_мавки_тангенс_р64(р64 значення) {
  return tan(значення);
}

р64 бібліотека_мавки_арксинус_р64(р64 значення) {
  return asin(значення);
}

р64 бібліотека_мавки_арккосинус_р64(р64 значення) {
  return acos(значення);
}

р64 бібліотека_мавки_арктангенс_р64(р64 значення) {
  return atan(значення);
}

р64 бібліотека_мавки_арктангенс2_р64(р64 а, р64 б) {
  return atan2(а, б);
}

р64 бібліотека_мавки_абсолютне_р64(р64 значення) {
  return fabs(значення);
}

р64 бібліотека_мавки_експонента_р64(р64 значення) {
  return exp(значення);
}

р64 бібліотека_мавки_корінь_р64(р64 значення) {
  return sqrt(значення);
}

р64 бібліотека_мавки_стеля_р64(р64 значення) {
  return ceil(значення);
}

р64 бібліотека_мавки_підлога_р64(р64 значення) {
  return floor(значення);
}

р64 бібліотека_мавки_округлити_р64(р64 значення) {
  return round(значення);
}

р64 бібліотека_мавки_остача_від_ділення_р64(р64 а, р64 б) {
  return fmod(а, б);
}

// видалити файл або папку за шляхом force recursively
логічне бібліотека_мавки_видалити(природне розмір_шляху, п8* дані_шляху) {
  char* шлях = (char*)malloc(розмір_шляху + 1);
  if (!шлях) {
    return false;
  }
  memcpy(шлях, дані_шляху, розмір_шляху);
  шлях[розмір_шляху] = '\0';

  struct stat статус;
  if (stat(шлях, &статус) == -1) {
    free(шлях);
    return false;
  }

  if (S_ISDIR(статус.st_mode)) {
    // Delete directory recursively
    DIR* папка = opendir(шлях);
    if (!папка) {
      free(шлях);
      return false;
    }

    struct dirent* запис;
    логічне успіх = true;
    while ((запис = readdir(папка)) != NULL) {
      if (strcmp(запис->d_name, ".") == 0 || strcmp(запис->d_name, "..") == 0) {
        continue;
      }

      size_t довжина_шляху = strlen(шлях) + strlen(запис->d_name) + 2;
      char* повний_шлях = (char*)malloc(довжина_шляху);
      if (!повний_шлях) {
        успіх = false;
        break;
      }
      snprintf(повний_шлях, довжина_шляху, "%s/%s", шлях, запис->d_name);

      if (!бібліотека_мавки_видалити(strlen(повний_шлях), (п8*)повний_шлях)) {
        успіх = false;
      }
      free(повний_шлях);
    }

    closedir(папка);

    if (!успіх) {
      free(шлях);
      return false;
    }

    int ok = (rmdir(шлях) == 0) ? true : false;
    free(шлях);
    return ok;
  } else {
    // Delete file
    логічне результат = unlink(шлях) == 0 ? true : false;
    free(шлях);
    return результат;
  }
}

логічне бібліотека_мавки_дописати_файл(природне розмір_шляху,
                                       п8* дані_шляху,
                                       природне розмір_даних,
                                       п8* дані_даних) {
  char* шлях = (char*)malloc(розмір_шляху + 1);
  if (!шлях) {
    return false;
  }
  memcpy(шлях, дані_шляху, розмір_шляху);
  шлях[розмір_шляху] = '\0';

  FILE* файл = fopen((const char*)шлях, "ab");
  if (!файл) {
    free(шлях);
    return false;
  }

  size_t записано = fwrite(дані_даних, 1, розмір_даних, файл);
  fclose(файл);
  free(шлях);

  return записано == розмір_даних;
}

логічне бібліотека_мавки_записати_файл(природне розмір_шляху,
                                       п8* дані_шляху,
                                       природне розмір_даних,
                                       п8* дані_даних) {
  char* шлях = (char*)malloc(розмір_шляху + 1);
  if (!шлях) {
    return false;
  }
  memcpy(шлях, дані_шляху, розмір_шляху);
  шлях[розмір_шляху] = '\0';

  FILE* файл = fopen((const char*)шлях, "wb");
  if (!файл) {
    free(шлях);
    return false;
  }

  size_t записано = fwrite(дані_даних, 1, розмір_даних, файл);
  fclose(файл);
  free(шлях);

  return записано == розмір_даних;
}

логічне бібліотека_мавки_прочитати_файл(природне розмір_шляху,
                                        п8* дані_шляху,
                                        Дані* вихід) {
  char* шлях = (char*)malloc(розмір_шляху + 1);
  if (!шлях) {
    return false;
  }
  memcpy(шлях, дані_шляху, розмір_шляху);
  шлях[розмір_шляху] = '\0';
  FILE* файл = fopen((const char*)шлях, "rb");
  if (!файл) {
    free(шлях);
    return false;
  }

  fseek(файл, 0, SEEK_END);
  вихід->розмір = ftell(файл);
  fseek(файл, 0, SEEK_SET);

  вихід->дані = (п8*)malloc(вихід->розмір);
  if (!вихід->дані) {
    fclose(файл);
    free(шлях);
    return false;
  }

  size_t прочитано = fread(вихід->дані, 1, вихід->розмір, файл);
  fclose(файл);

  if (прочитано != вихід->розмір) {
    free(вихід->дані);
    free(шлях);
    return false;
  }

  free(шлях);

  return true;
}

логічне бібліотека_мавки_створити_папку(природне розмір_шляху, п8* дані_шляху) {
  char* шлях = (char*)malloc(розмір_шляху + 1);
  if (!шлях) {
    return false;
  }
  memcpy(шлях, дані_шляху, розмір_шляху);
  шлях[розмір_шляху] = '\0';

  // Check if directory already exists
  struct stat статус;
  if (stat(шлях, &статус) == 0 && S_ISDIR(статус.st_mode)) {
    free(шлях);
    return true;
  }

  // Create a copy to parse
  char* копія_шляху = (char*)malloc(розмір_шляху + 1);
  if (!копія_шляху) {
    free(шлях);
    return false;
  }
  memcpy(копія_шляху, дані_шляху, розмір_шляху);
  копія_шляху[розмір_шляху] = '\0';

  // Create directories recursively
  for (size_t я = 0; я < розмір_шляху; я++) {
    if (копія_шляху[я] == '/') {
      копія_шляху[я] = '\0';
      if> 0) { // Skip if it's the root /
        if (mkdir((const char*)копія_шляху, 0755) == -1) {
          if (errno != EEXIST) {
            free(копія_шляху);
            free(шлях);
            return false;
          }
        }
      }
      копія_шляху[я] = '/';
    }
  }

  // Create the final directory
  if (mkdir((const char*)шлях, 0755) == -1) {
    if (errno != EEXIST) {
      free(копія_шляху);
      free(шлях);
      return false;
    }
    // EEXIST occurred - verify it's actually a directory, not a file
    if (stat(шлях, &статус) == 0 && !S_ISDIR(статус.st_mode)) {
      free(копія_шляху);
      free(шлях);
      return false;
    }
  }

  free(копія_шляху);
  free(шлях);
  return true;
}

static логічне встановити_закриття_при_виконанні(int дескриптор) {
  int прапори = fcntl(дескриптор, F_GETFD);
  if (прапори == -1) {
    return false;
  }
  return fcntl(дескриптор, F_SETFD, прапори | FD_CLOEXEC) != -1;
}

static логічне прочитати_потік_в_дані(int дескриптор, Дані* вихід) {
  const size_t крок = 4096;
  size_t ємність = 0;
  size_t використано = 0;
  п8* буфер = NULL;

  while (true) {
    if (використано + крок > ємність) {
      size_t наступна_ємність = ємність == 0 ? крок : ємність * 2;
      if (наступна_ємність < використано + крок) {
        наступна_ємність = використано + крок;
      }
      п8* новий_буфер = (п8*)realloc(буфер, наступна_ємність);
      if (!новий_буфер) {
        free(буфер);
        return false;
      }
      буфер = новий_буфер;
      ємність = наступна_ємність;
    }

    ssize_t прочитано = read(дескриптор, буфер + використано, крок);
    if (прочитано < 0) {
      if (errno == EINTR) {
        continue;
      }
      free(буфер);
      return false;
    }
    if (прочитано == 0) {
      break;
    }

    використано += (size_t)прочитано;
  }

  if (використано == 0) {
    free(буфер);
    вихід->розмір = 0;
    вихід->дані = NULL;
    return true;
  }

  п8* обрізаний_буфер = (п8*)realloc(буфер, використано);
  if (обрізаний_буфер) {
    буфер = обрізаний_буфер;
  }

  вихід->розмір = використано;
  вихід->дані = буфер;
  return true;
}

void бібліотека_мавки_виконати(природне розмір,
                               п8* дані,
                               РезультатВиконання* результат_виконання) {
  if (!результат_виконання) {
    return;
  }

  результат_виконання->стдвив.розмір = 0;
  результат_виконання->стдвив.дані = NULL;
  результат_виконання->стдпом.розмір = 0;
  результат_виконання->стдпом.дані = NULL;
  результат_виконання->код = -1;

  if (розмір == 0 || !дані) {
    return;
  }

  char* команда = (char*)malloc(розмір + 1);
  if (!команда) {
    return;
  }
  memcpy(команда, дані, розмір);
  команда[розмір] = '\0';

  char* argv[4];
  argv[0] = "/bin/sh";
  argv[1] = "-c";
  argv[2] = команда;
  argv[3] = NULL;

  int stdout_pipe[2] = {-1, -1};
  int stderr_pipe[2] = {-1, -1};
  pid_t pid = -1;

  if (pipe(stdout_pipe) == -1 || pipe(stderr_pipe) == -1) {
    free(команда);
    return;
  }

  встановити_закриття_при_виконанні(stdout_pipe[0]);
  встановити_закриття_при_виконанні(stdout_pipe[1]);
  встановити_закриття_при_виконанні(stderr_pipe[0]);
  встановити_закриття_при_виконанні(stderr_pipe[1]);

  pid = fork();
  if (pid == -1) {
    free(команда);
    goto cleanup;
  }

  if (pid == 0) {
    close(stdout_pipe[0]);
    close(stderr_pipe[0]);

    if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1 ||
        dup2(stderr_pipe[1], STDERR_FILENO) == -1) {
      _exit(127);
    }

    close(stdout_pipe[1]);
    close(stderr_pipe[1]);

    execvp(argv[0], argv);
    _exit(127);
  }

  close(stdout_pipe[1]);
  close(stderr_pipe[1]);
  stdout_pipe[1] = -1;
  stderr_pipe[1] = -1;

  прочитати_потік_в_дані(stdout_pipe[0], &результат_виконання->стдвив);
  прочитати_потік_в_дані(stderr_pipe[0], &результат_виконання->стдпом);

  {
    int стати;
    логічне дочекався = false;

    while (true) {
      if (waitpid(pid, &стати, 0) == -1) {
        if (errno == EINTR) {
          continue;
        }
        break;
      }

      дочекався = true;
      break;
    }

    if (дочекався) {
      if (WIFEXITED(стати)) {
        результат_виконання->код = WEXITSTATUS(стати);
      } else if (WIFSIGNALED(стати)) {
        результат_виконання->код = 128 + WTERMSIG(стати);
      }
    }
  }

cleanup:
  if (stdout_pipe[0] != -1) {
    close(stdout_pipe[0]);
  }
  if (stderr_pipe[0] != -1) {
    close(stderr_pipe[0]);
  }
  free(команда);
}

void бібліотека_мавки_виконати_аргументи(
    природне кількість_аргументів,
    ю8* аргументи,
    РезультатВиконання* результат_виконання) {
  if (!результат_виконання) {
    return;
  }

  результат_виконання->стдвив.розмір = 0;
  результат_виконання->стдвив.дані = NULL;
  результат_виконання->стдпом.розмір = 0;
  результат_виконання->стдпом.дані = NULL;
  результат_виконання->код = -1;

  if (кількість_аргументів == 0 || !аргументи) {
    return;
  }

  char** argv = (char**)malloc((кількість_аргументів + 1) * sizeof(char*));
  if (!argv) {
    return;
  }

  for (природне i = 0; i < кількість_аргументів; ++i) {
    argv[i] = (char*)аргументи[i].дані;
  }
  argv[кількість_аргументів] = NULL;

  int stdout_pipe[2] = {-1, -1};
  int stderr_pipe[2] = {-1, -1};
  pid_t pid = -1;

  if (pipe(stdout_pipe) == -1 || pipe(stderr_pipe) == -1) {
    goto cleanup;
  }

  встановити_закриття_при_виконанні(stdout_pipe[0]);
  встановити_закриття_при_виконанні(stdout_pipe[1]);
  встановити_закриття_при_виконанні(stderr_pipe[0]);
  встановити_закриття_при_виконанні(stderr_pipe[1]);

  pid = fork();
  if (pid == -1) {
    goto cleanup;
  }

  if (pid == 0) {
    close(stdout_pipe[0]);
    close(stderr_pipe[0]);

    if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1 ||
        dup2(stderr_pipe[1], STDERR_FILENO) == -1) {
      _exit(127);
    }

    close(stdout_pipe[1]);
    close(stderr_pipe[1]);

    execvp(argv[0], argv);
    _exit(127);
  }

  close(stdout_pipe[1]);
  close(stderr_pipe[1]);
  stdout_pipe[1] = -1;
  stderr_pipe[1] = -1;

  прочитати_потік_в_дані(stdout_pipe[0], &результат_виконання->стдвив);
  прочитати_потік_в_дані(stderr_pipe[0], &результат_виконання->стдпом);

  {
    int стати;
    логічне дочекався = false;

    while (true) {
      if (waitpid(pid, &стати, 0) == -1) {
        if (errno == EINTR) {
          continue;
        }
        break;
      }
      дочекався = true;
      break;
    }

    if (дочекався) {
      if (WIFEXITED(стати)) {
        результат_виконання->код = (ц32)WEXITSTATUS(стати);
      } else if (WIFSIGNALED(стати)) {
        результат_виконання->код = (ц32)(128 + WTERMSIG(стати));
      } else {
        результат_виконання->код = -1;
      }
    }
  }

cleanup:
  if (stdout_pipe[0] != -1) {
    close(stdout_pipe[0]);
  }
  if (stdout_pipe[1] != -1) {
    close(stdout_pipe[1]);
  }
  if (stderr_pipe[0] != -1) {
    close(stderr_pipe[0]);
  }
  if (stderr_pipe[1] != -1) {
    close(stderr_pipe[1]);
  }
  free(argv);
}