// SPDX-FileCopyrightText: 2017 Google LLC
// SPDX-License-Identifier: Apache-2.0

#include "internal/stack_line_reader.h"
#include "internal/filesystem.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>

void StackLineReader_Initialize(StackLineReader* reader, int fd)
{
    reader->view.ptr = reader->buffer;
    reader->view.size = 0;
    reader->skip_mode = false;
    reader->fd = fd;
}

// Replaces the content of buffer with bytes from the file.
static int LoadFullBuffer(StackLineReader* reader)
{
    const int read = CpuFeatures_ReadFile(reader->fd, reader->buffer,
        STACK_LINE_READER_BUFFER_SIZE);
    assert(read >= 0);
    reader->view.ptr = reader->buffer;
    reader->view.size = read;
    return read;
}

// Appends with bytes from the file to buffer, filling the remaining space.
static int LoadMore(StackLineReader* reader)
{
    char* const ptr = reader->buffer + reader->view.size;
    const size_t size_to_read = STACK_LINE_READER_BUFFER_SIZE - reader->view.size;
    const int read = CpuFeatures_ReadFile(reader->fd, ptr, size_to_read);
    assert(read >= 0);
    assert(read <= (int)size_to_read);
    reader->view.size += read;
    return read;
}

static int IndexOfEol(StackLineReader* reader)
{
    return CpuFeatures_StringView_IndexOfChar(reader->view, '\n');
}

// Relocate buffer's pending bytes at the beginning of the array and fills the
// remaining space with bytes from the file.
static int BringToFrontAndLoadMore(StackLineReader* reader)
{
    if (reader->view.size && reader->view.ptr != reader->buffer)
        {
            memmove(reader->buffer, reader->view.ptr, reader->view.size);
        }
    reader->view.ptr = reader->buffer;
    return LoadMore(reader);
}

// Loads chunks of buffer size from disks until it contains a newline character
// or end of file.
static void SkipToNextLine(StackLineReader* reader)
{
    for (;;)
        {
            const int read = LoadFullBuffer(reader);
            if (read == 0)
                {
                    break;
                }
            else
                {
                    const int eol_index = IndexOfEol(reader);
                    if (eol_index >= 0)
                        {
                            reader->view =
                                CpuFeatures_StringView_PopFront(reader->view, eol_index + 1);
                            break;
                        }
                }
        }
}

static LineResult CreateLineResult(bool eof, bool full_line, StringView view)
{
    LineResult result;
    result.eof = eof;
    result.full_line = full_line;
    result.line = view;
    return result;
}

// Helper methods to provide clearer semantic in StackLineReader_NextLine.
static LineResult CreateEOFLineResult(StringView view)
{
    return CreateLineResult(true, true, view);
}

static LineResult CreateTruncatedLineResult(StringView view)
{
    return CreateLineResult(false, false, view);
}

static LineResult CreateValidLineResult(StringView view)
{
    return CreateLineResult(false, true, view);
}

LineResult StackLineReader_NextLine(StackLineReader* reader)
{
    if (reader->skip_mode)
        {
            SkipToNextLine(reader);
            reader->skip_mode = false;
        }
    {
        const bool can_load_more =
            reader->view.size < STACK_LINE_READER_BUFFER_SIZE;
        int eol_index = IndexOfEol(reader);
        if (eol_index < 0 && can_load_more)
            {
                const int read = BringToFrontAndLoadMore(reader);
                if (read == 0)
                    {
                        return CreateEOFLineResult(reader->view);
                    }
                eol_index = IndexOfEol(reader);
            }
        if (eol_index < 0)
            {
                reader->skip_mode = true;
                return CreateTruncatedLineResult(reader->view);
            }
        {
            StringView line =
                CpuFeatures_StringView_KeepFront(reader->view, eol_index);
            reader->view =
                CpuFeatures_StringView_PopFront(reader->view, eol_index + 1);
            return CreateValidLineResult(line);
        }
    }
}
