#include <libgen.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "mavka/prystriy.h"

static void пристрій_мавки_вивести_колір(природне значення) {
  if (значення == КолірВиводуСтандартний) {
    printf("\033[0m");
  }

  if (значення == КолірВиводуЧервоний) {
    printf("\033[0;31m");
  }

  if (значення == КолірВиводуЗелений) {
    printf("\033[0;32m");
  }

  if (значення == КолірВиводуСиній) {
    printf("\033[0;34m");
  }

  if (значення == КолірВиводуЖовтий) {
    printf("\033[0;33m");
  }
}

extern void пристрій_мавки_вивести_сирі_дані(п8* дані, природне розмір) {
  fwrite(дані, 1, розмір, stdout);
}

extern void пристрій_мавки_вивести_шлях(природне колір,
                                        п8* дані,
                                        природне розмір) {
  пристрій_мавки_вивести_колір(колір);

  fwrite(дані, 1, розмір, stdout);

  пристрій_мавки_вивести_колір(КолірВиводуСтандартний);
}

extern void пристрій_мавки_вивести_ю8(природне колір,
                                      п8* дані,
                                      природне розмір) {
  пристрій_мавки_вивести_колір(колір);

  fwrite(дані, 1, розмір, stdout);

  пристрій_мавки_вивести_колір(КолірВиводуСтандартний);
}

extern void пристрій_мавки_вийти(ц32 код_виходу) {
  exit(код_виходу);
}

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

  FILE* файл = fopen(шлях_рядок, "rb");
  пристрій_мавки_звільнити(шлях_рядок);
  if (!файл) {
    return false;
  }

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

  *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
  if (!*вихід_даних) {
    fclose(файл);
    return false;
  }

  fread(*вихід_даних, 1, *вихід_розміру, файл);
  fclose(файл);
  return true;
}

логічне пристрій_мавки_перетворити_п64_в_ю8(п64 значення,
                                            п8** вихід_даних,
                                            природне* вихід_розміру) {
  char буфер[32];
  int довжина =
      snprintf(буфер, sizeof(буфер), "%llu", (unsigned long long)значення);
  if (довжина < 0) {
    return false;
  }
  *вихід_розміру = (природне)довжина;
  *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
  if (!*вихід_даних) {
    return false;
  }
  for (природне і = 0; і < *вихід_розміру; і++) {
    (*вихід_даних)[і] = (п8)буфер[і];
  }
  return true;
}

логічне пристрій_мавки_перетворити_ц64_в_ю8(ц64 значення,
                                            п8** вихід_даних,
                                            природне* вихід_розміру) {
  char буфер[32];
  int довжина = snprintf(буфер, sizeof(буфер), "%lld", (long long)значення);
  if (довжина < 0) {
    return false;
  }
  *вихід_розміру = (природне)довжина;
  *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
  if (!*вихід_даних) {
    return false;
  }
  for (природне і = 0; і < *вихід_розміру; і++) {
    (*вихід_даних)[і] = (п8)буфер[і];
  }
  return true;
}

// 123.0 => "123"
// 123.456 => "123.456"
// -0.00123 => "-0.00123"
// 1.2e+30 => "1.2" 30
// 1.2e-30 => "1.2" -30
логічне пристрій_мавки_перетворити_р64_в_ю8(р64 значення,
                                            п8** вихід_даних,
                                            природне* вихід_розміру,
                                            ціле* вихід_розміру_експоненти) {
  char буфер[64];
  int довжина;

  if (isnan(значення)) {
    char nan_str[] = "невизначеність";
    *вихід_розміру = sizeof(nan_str) - 1;
    *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
    if (!*вихід_даних)
      return false;
    memcpy(*вихід_даних, nan_str, *вихід_розміру);
    *вихід_розміру_експоненти = 0;
    return true;
  }

  if (isinf(значення)) {
    char* inf_str = значення > 0 ? "нескінченність" : "-нескінченність";
    *вихід_розміру = значення > 0 ? sizeof("нескінченність") - 1
                                  : sizeof("-нескінченність") - 1;
    *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
    if (!*вихід_даних)
      return false;
    memcpy(*вихід_даних, inf_str, *вихід_розміру);
    *вихід_розміру_експоненти = 0;
    return true;
  }

  // Знаходимо найкоротше представлення, яке round-trips
  // Пробуємо від 15 до 17 цифр точності
  for (int precision = 15; precision <= 17; precision++) {
    довжина = snprintf(буфер, sizeof(буфер), "%.*g", precision, значення);
    if (довжина < 0 || довжина >= (int)sizeof(буфер)) {
      continue;
    }

    // Перевіряємо чи round-trip працює
    р64 parsed = strtod(буфер, NULL);
    if (parsed == значення) {
      break;
    }
  }

  if (довжина < 0 || довжина >= (int)sizeof(буфер)) {
    return false;
  }

  char* e_pos = strchr(буфер, 'e');
  if (e_pos != NULL) {
    *вихід_розміру_експоненти = (ціле)strtol(e_pos + 1, NULL, 10);

    природне довжина_мантиси = (природне)(e_pos - буфер);

    *вихід_розміру = довжина_мантиси;
    *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
    if (!*вихід_даних)
      return false;

    for (природне і = 0; і < довжина_мантиси; і++) {
      (*вихід_даних)[і] = (п8)буфер[і];
    }
  } else {
    *вихід_розміру_експоненти = 0;

    *вихід_розміру = (природне)довжина;
    *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
    if (!*вихід_даних)
      return false;

    for (природне і = 0; і < *вихід_розміру; і++) {
      (*вихід_даних)[і] = (п8)буфер[і];
    }
  }

  return true;
}

extern р64 пристрій_мавки_піднести_до_степеня_р64(р64 значення, р64 степінь) {
  return pow(значення, степінь);
}

extern логічне пристрій_мавки_перевірити_чи_шлях_існує_і_є_файлом(
    п8* дані_шляху,
    природне розмір_шляху) {
  char* шлях_рядок = (char*)пристрій_мавки_виділити(розмір_шляху + 1);
  for (природне і = 0; і < розмір_шляху; і++) {
    шлях_рядок[і] = (char)дані_шляху[і];
  }
  шлях_рядок[розмір_шляху] = '\0';

  struct stat буфер_статистики;
  логічне результат = false;
  if (stat(шлях_рядок, &буфер_статистики) == 0) {
    if (S_ISREG(буфер_статистики.st_mode)) {
      результат = true;
    }
  }

  пристрій_мавки_звільнити(шлях_рядок);
  return результат;
}

extern логічне пристрій_мавки_отримати_абсолютний_шлях(
    п8* дані_шляху,
    природне розмір_шляху,
    п8** вихід_даних,
    природне* вихід_розміру) {
  char* шлях_рядок = (char*)пристрій_мавки_виділити(розмір_шляху + 1);
  for (природне і = 0; і < розмір_шляху; і++) {
    шлях_рядок[і] = (char)дані_шляху[і];
  }
  шлях_рядок[розмір_шляху] = '\0';

  char* абсолютний_шлях = realpath(шлях_рядок, NULL);
  пристрій_мавки_звільнити(шлях_рядок);

  if (абсолютний_шлях == NULL) {
    return false;
  }

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

  for (природне і = 0; і < довжина; і++) {
    (*вихід_даних)[і] = (п8)абсолютний_шлях[і];
  }

  пристрій_мавки_звільнити(абсолютний_шлях);
  return true;
}

#ifdef PROGRAM_USE_READLINE
#include <readline/readline.h>

static char* do_readline(char* prefix, size_t prefix_size, size_t* out_len) {
  char* data = readline(prefix);
  if (!data) {
    return NULL;
  }

  *out_len = strlen(data);
  return data;
}
#else
static char* do_readline(char* prefix, size_t prefix_size, size_t* out_len) {
  пристрій_мавки_вивести_ю8(КолірВиводуСтандартний, (п8*)prefix,
                            (природне)prefix_size);

  size_t cap = 128;
  size_t len = 0;
  char* buf = пристрій_мавки_виділити(cap);
  if (!buf)
    return NULL;

  int c;
  while ((c = getchar()) != EOF) {
    if (c == '\n')
      break; // stop, but DON'T store '\n'

    if (len + 1 >= cap) {
      cap *= 2;
      char* tmp = пристрій_мавки_перевиділити(buf, cap);
      if (!tmp) {
        пристрій_мавки_звільнити(buf);
        return NULL;
      }
      buf = tmp;
    }

    buf[len++] = c;
  }

  // If nothing read and EOF, return NULL
  if (len == 0 && c == EOF) {
    пристрій_мавки_звільнити(buf);
    return NULL;
  }

  buf[len] = '\0';

  if (out_len)
    *out_len = len;

  return buf;
}
#endif

extern логічне пристрій_мавки_читати_ю8(п8* дані_перед,
                                        природне розмір_перед,
                                        п8** вихід_даних,
                                        природне* вихід_розміру,
                                        природне* вихід_кінець_файлу,
                                        природне дозволити_історію) {
  size_t len;
  char* data = do_readline((char*)дані_перед, (size_t)розмір_перед, &len);

  if (!data) {
    *вихід_кінець_файлу = true;

    return true;
  }

  *вихід_розміру = len;
  *вихід_даних = (п8*)data;
  *вихід_кінець_файлу = false;

  return true;
}

extern логічне пристрій_мавки_читати_ю8_асинхронно(
    п8* дані_перед,
    природне розмір_перед,
    void (*обробник)(п8* дані,
                     природне розмір,
                     природне кінець_файлу,
                     адреса аргумент),
    адреса аргумент,
    природне дозволити_історію) {
  size_t len;
  char* data = do_readline((char*)дані_перед, (size_t)розмір_перед, &len);

  if (!data) {
    обробник((п8*)data, (природне)len, true, аргумент);

    return true;
  }

  обробник((п8*)data, (природне)len, false, аргумент);

  return true;
}

extern логічне пристрій_мавки_отримати_поточний_шлях_процесу(
    п8** вихід_даних,
    природне* вихід_розміру) {
  char* поточна_тека = getcwd(NULL, 0);
  if (поточна_тека == NULL) {
    return false;
  }

  *вихід_розміру = strlen(поточна_тека);
  *вихід_даних = (п8*)пристрій_мавки_виділити(*вихід_розміру);
  if (!*вихід_даних) {
    пристрій_мавки_звільнити(поточна_тека);
    return false;
  }

  memcpy(*вихід_даних, поточна_тека, *вихід_розміру);
  пристрій_мавки_звільнити(поточна_тека);
  return true;
}

#include "../unix_wasm/prystriy_unix_wasm64.c"