#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "buffer.h"
#include "erow.h"
#include "utils.h"

static void buffer_free_rows(struct buffer *buffer);
static char *buffer_get_string(struct buffer *buffer, size_t *len_p);

struct buffer *buffer_create(void) {
    struct buffer *buffer = malloc(sizeof(struct buffer));

    buffer->filename = NULL;

    buffer->cx = buffer->cy = buffer->rx = 0;
    buffer->row_off = buffer->col_off = 0;

    buffer->rows = NULL;
    buffer->n_rows = 0;

    buffer->modified = false;

    return buffer;
}

ERRCODE buffer_read_file(struct buffer *buffer, const char *filename) {
    ERRCODE errcode = 0;

    if (buffer->filename) free(buffer->filename);
    size_t filename_len = strlen(filename);
    buffer->filename = malloc(filename_len);
    memcpy(buffer->filename, filename, filename_len);

    if (buffer->rows)
        buffer_free_rows(buffer);

    size_t buf_cap = 64;
    char *buf = malloc(buf_cap);

    FILE *file = fopen(filename, "r");
    if (file == NULL)
        RETURN(-1);

    while (true) {
        char c;
        size_t buf_size = 0;

        while ((c = getc(file)) != '\n' && c != EOF) {
            if (buf_size >= buf_cap)
                buf = realloc(buf, buf_cap *= 2);

            buf[buf_size++] = c;
        }

        struct erow* erow = erow_create(buf, buf_size, buffer);
        buffer_insert_row(buffer, erow, buffer->n_rows);

        if (c == EOF)
            break;
    }

    if (buffer->rows[buffer->n_rows - 1]->n_chars == 0)
        buffer_delete_row(buffer, buffer->n_rows - 1);

END:
    if (file != NULL)
        fclose(file);

    if (buf != NULL)
        free(buf);

    buffer->modified = false;

    return errcode;
}

ERRCODE buffer_write_file(struct buffer *buffer, size_t *bytes_written) {
    ERRCODE errcode = 0;

    FILE *file = NULL;
    char *write_buffer = NULL;

    if (buffer->filename == NULL)
        RETURN(-1);

    size_t n_chars;
    write_buffer = buffer_get_string(buffer, &n_chars);

    file = fopen(buffer->filename, "w");
    if (file == NULL)
        RETURN(-2);

    *bytes_written = fwrite(write_buffer, 1, n_chars, file);
    if (*bytes_written != n_chars)
        RETURN(-3);

END:
    if (file != NULL)
        fclose(file);

    if (write_buffer)
        free(write_buffer);

    if (errcode == 0)
        buffer->modified = false;

    return errcode;
}

ERRCODE buffer_get_row_chars(struct buffer *buffer, char **chars, size_t *n_chars, int at) {
    if (buffer == NULL)
        return -1;

    if (!(0 <= at && at <= buffer->n_rows))
        return -2;

    if (chars) *chars = buffer->rows[at]->chars;
    if (n_chars) *n_chars = buffer->rows[at]->n_chars;

    return 0;
}

ERRCODE buffer_get_row_rchars(struct buffer *buffer, char **rchars, size_t *n_rchars, int at) {
    if (buffer == NULL)
        return -1;

    if (!(0 <= at && at <= buffer->n_rows))
        return -2;

    if (rchars) *rchars = buffer->rows[at]->rchars;
    if (n_rchars) *n_rchars = buffer->rows[at]->n_rchars;

    return 0;
}

void buffer_insert_row(struct buffer *buffer, struct erow *erow, int at) {
    if (!(0 <= at && at <= buffer->n_rows))
        return;

    buffer->rows = realloc(buffer->rows, sizeof(struct erow *) * (buffer->n_rows + 1));
    memmove(buffer->rows + at + 1, buffer->rows + at, sizeof(struct erow *) * (buffer->n_rows - at));

    buffer->rows[at] = erow;
    buffer->n_rows++;
}

void buffer_delete_row(struct buffer *buffer, int at) {
    if (!(0 <= at && at < buffer->n_rows))
        return;

    erow_free(buffer->rows[at]);
    memmove(buffer->rows + at, buffer->rows + at + 1, sizeof(struct erow *) * (buffer->n_rows - at - 1));
    buffer->n_rows--;

    buffer->modified = true;
}

struct erow *buffer_get_crow(struct buffer *buffer) {
    return (buffer->cy < buffer->n_rows ? buffer->rows[buffer->cy]: NULL);
}

void buffer_free(struct buffer *buffer) {
    free(buffer->filename);

    buffer_free_rows(buffer);
}

size_t buffer_get_crow_len(struct buffer *buffer) {
    struct erow *crow = buffer_get_crow(buffer);

    return crow ? crow->n_chars : 0;
}

static void buffer_free_rows(struct buffer *buffer) {
    for (int i = 0; i < buffer->n_rows; i++)
        erow_free(buffer->rows[i]);

    free(buffer->rows);

    buffer->rows = NULL;
    buffer->n_rows = 0;
}

static char *buffer_get_string(struct buffer *buffer, size_t *n_chars) {
    *n_chars = 0;

    for (int i = 0; i < buffer->n_rows; i++)
        *n_chars += buffer->rows[i]->n_chars + 1;

    char *write_buffer = malloc(*n_chars);
    for (int i = 0, j = 0; i < buffer->n_rows; i++) {
        struct erow *row = buffer->rows[i];

        memcpy(write_buffer + j, row->chars, row->n_chars);
        j += row->n_chars;
        write_buffer[j++] = '\n';
    }

    return write_buffer;
}