/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The Initial Developer of this code is David Baum.
 * Portions created by David Baum are Copyright (C) 1999 David Baum.
 * All Rights Reserved.
 *
 * Portions created by John Hansen are Copyright (C) 2005 John Hansen.
 * All Rights Reserved.
 *
 */

#include <cstdio>
#include <cstring>

#include "Buffer.h"

using std::fopen;
using std::memcpy;
using std::strlen;

#ifdef DEBUG
using std::strcpy;
#endif

#define CR  0x0d
#define LF  0x0a

#define kDefaultBufferSize 10

Buffer::Buffer() :
    fName(0),
    fData(0),
    fLength(0)
{
}


Buffer::~Buffer()
{
    delete [] fName;
    delete [] fData;
}


bool Buffer::Create(const char *name, const char *pathname)
{
    FILE *fp = fopen(pathname, "rb");
    if (!fp) return false;

    Create(name, fp);
    fclose(fp);

    return true;
}


void Buffer::Create(const char *name, FILE *fp)
{
    // figure out length
    if (fseek(fp,0,SEEK_END)==0) {
        // if stream supports seeking, then we can determine total size,
        // allcoate enough storage, and read the entire thing at once
        fLength = ftell(fp);
        fseek(fp, 0, SEEK_SET);

        // allow room for terminating NL
        int allocate = fLength + 1;
        fData = new char[allocate];

        fread(fData, 1, fLength, fp);
    }
    else {
        // This code is for io streams (such as redirected stdin under MPW)
        // which don't support seeking.  The data from the stream is read into
        // a dynamically growing array.  This is a lot less efficient than
        // the previous case which knew the total lenght beforehand.
        int max = kDefaultBufferSize;
        fData = new char[max];
        fLength = 0;

        int c;
        while ((c=getc(fp)) != EOF) {
            fData[fLength++] = (char)c;

            if (fLength == max) {
                // grow buffer - this needs to be done even on the last
                // char in the file since a terminating NL will be added
                // later on
                max *= 2;
                char *newBuf = new char[max];
                memcpy(newBuf, fData, fLength);
                delete [] fData;
                fData = newBuf;
            }
        }
    }
    FinishCreate(name);
}


void Buffer::Create(const char *name, const char *data, int length)
{
    // allcoate memory (allow for trailing NL)
    fData = new char[length + 1];
    fLength = length;
    memcpy(fData, data, length);

    FinishCreate(name);
}


void Buffer::FinishCreate(const char *name)
{
    fName = new char[strlen(name) + 1];
    strcpy(fName, name);

    ConvertLineEndings();

    // do this after ConvertLineEndings() since mac files might
    // end with CR and \n is an LF, so CRLF would get reduced if
    // \n was added before conversion
    fData[fLength++] = '\n';
}


const char *Buffer::GetLine(int line) const
{
    if (line < 1) return 0;

    const char *ptr = fData;
    const char *end = fData + fLength;

    --line;
    while (line) {
        if (ptr == end)
            return 0;
        if (*ptr++ == '\n')
            --line;
    }

    return ptr;
}


/**
 * int Buffer::FindLine(int &offset) const
 *
 * return the line number given an offset.  Also adjust the offset
 * to the start of the line.
 */

int Buffer::FindLine(int &offset) const
{
    const char *ptr = fData;
    const char *lineStart = fData;
    int line = 1;

    // clip the offset to fLength-1 since fData[fLength-1] is implicitly
    // the last line of the buffer and no line can start after it
    const char *end = ptr + ((offset < (fLength-1)) ? offset : fLength-1);

    while (ptr < end) {
        if (*ptr++ == '\n') {
            ++line;
            lineStart = ptr; // ptr is already adjusted past the NL
        }
    }

    offset = lineStart - fData;
    return line;
}


/**
 * int Buffer::FindStartOfLine(int offset) const
 *
 * return the offset of the first character in the line containing the
 * specified offset.
 */

int Buffer::FindStartOfLine(int offset) const
{
    const char *ptr = fData + offset;
    while (ptr > fData) {
        --ptr;
        if (*ptr == '\n') return (ptr - fData + 1);
    }

    return 0;
}



/**
 * int Buffer::FindEndOfLine(int offset) const
 *
 * return the offset of the newline ending the line containing the
 * specified offset.
 */

int Buffer::FindEndOfLine(int offset) const
{
    const char *ptr = fData + offset;
    const char *end = fData + fLength;

    while (ptr < end) {
        if (*ptr == '\n') return ptr - fData;
        ++ptr;
    }

    return fLength - 1;
}


void Buffer::ConvertLineEndings()
{
    const char *src, *end;
    char *dst;

    src = fData;
    end = src + fLength;
    dst = fData;

    while (src < end) {
        char c = *src++;

        switch(c) {
            case CR:
                *dst++ = '\n';
                // skip second part of CR-LF pair
                if (src < end && *src==LF)
                    src++;
                break;
            case LF:
                *dst++ = '\n';
                break;
            default:
                *dst++ = c;
                break;
        }
    }

    fLength = dst - fData;
}

