/*------------------------------------------------------------*/
/* filename -       HelpBase.cpp                              */
/*                                                            */
/* function(s)                                                */
/*                  Member function(s) of following classes   */
/*                      THelpTopic                            */
/*                      THelpIndex                            */
/*                      THelpFile                             */
/*------------------------------------------------------------*/
/*
 *      Turbo Vision - Version 2.0
 *
 *      Copyright (c) 1994 by Borland International
 *      All Rights Reserved.
 *
 */

#define Uses_TStreamableClass
#define Uses_TPoint
#define Uses_TStreamable
#define Uses_ipstream
#define Uses_opstream
#define Uses_fpstream
#define Uses_TRect
#include "tv.h"

#if !defined( __HELP_H )
#include "helpbase.h"
#endif  // __HELP_H

#if !defined( __UTIL_H )
#include "util.h"
#endif  // __UTIL_H

#if !defined( __STRING_H )
#include <string.h>
#endif  // __STRING_H

#if !defined( __LIMITS_H )
#include <limits.h>
#endif  // __LIMITS_H

#if !defined( __STAT_H )
#include <sys\stat.h>
#endif  // __STAT_H

#if !defined( __CTYPE_H )
#include <ctype.h>
#endif  // __CTYPE_H

#if !defined( __IO_H )
#include <io.h>
#endif  // __IO_H

#pragma warn -dsz

TCrossRefHandler crossRefHandler = notAssigned;

// THelpTopic

const char * const _NEAR THelpTopic::name = "THelpTopic";

void THelpTopic::write( opstream& os )
{
    writeParagraphs( os );
    writeCrossRefs( os );

}

void *THelpTopic::read( ipstream& is )
{
    readParagraphs( is );
    readCrossRefs( is );
    width = 0;
    lastLine = INT_MAX;
    return this;
}

TStreamable *THelpTopic::build()
{
    return new THelpTopic( streamableInit );
}


TStreamableClass RHelpTopic( THelpTopic::name,
                                  THelpTopic::build,
                                  __DELTA(THelpTopic)
                                );

THelpTopic::THelpTopic() : TObject()
{
    paragraphs = 0;
    numRefs = 0;
    crossRefs = 0;
    width = 0;
    lastOffset = 0;
    lastLine = INT_MAX;
    lastParagraph = 0;
};

void THelpTopic::readParagraphs( ipstream& s )
{
    int  i;
    ushort size;
    TParagraph **pp;
    int temp;

    s >> i;
    pp = &paragraphs;
    while ( i > 0)
    {
        s >> size;
        *pp = new TParagraph;
        (*pp)->text = new char[size];
        (*pp)->size = (ushort) size;
    s >> temp;
        (*pp)->wrap = Boolean(temp);
        s.readBytes((*pp)->text, (*pp)->size);
        pp = &((*pp)->next);
        --i;
    }
    *pp = 0;
}

void THelpTopic::readCrossRefs( ipstream& s )
{
    int i;
    TCrossRef *crossRefPtr;

    s >> numRefs;
    crossRefs = new TCrossRef[numRefs];
    for (i = 0; i < numRefs; ++i)
        {
        crossRefPtr = (TCrossRef *)crossRefs + i;
        s.readBytes(crossRefPtr, sizeof(TCrossRef));
        }
}

void THelpTopic::disposeParagraphs()
{
    TParagraph *p, *t;

    p = paragraphs;
    while (p != 0)
        {
        t = p;
        p = p->next;
        delete t->text;
        delete t;
        }
}


THelpTopic::~THelpTopic()
{
    TCrossRef *crossRefPtr;

    disposeParagraphs();
    if (crossRefs != 0)
       {
       crossRefPtr = (TCrossRef *)crossRefs;
       delete [numRefs] crossRefPtr;
       }
}

void THelpTopic::addCrossRef( TCrossRef ref )
{
    TCrossRef *p;
    TCrossRef *crossRefPtr;

    p =  new TCrossRef[numRefs+1];
    if (numRefs > 0)
        {
        crossRefPtr = crossRefs;
        memmove(p, crossRefPtr, numRefs * sizeof(TCrossRef));
        delete [numRefs] crossRefPtr;
        }
    crossRefs = p;
    crossRefPtr = crossRefs + numRefs;
    *crossRefPtr = ref;
    ++numRefs;
}


void THelpTopic::addParagraph( TParagraph *p )
{
    TParagraph  *pp, *back;

    if (paragraphs == 0)
        paragraphs = p;
    else
        {
        pp = paragraphs;
        back = pp;
        while (pp != 0)
            {
            back = pp;
            pp = pp->next;
            }
        back->next = p;
        }
    p->next = 0;
}

void THelpTopic::getCrossRef( int i, TPoint& loc, uchar& length,
         int& ref )
{
    int oldOffset, curOffset, offset, paraOffset;
    TParagraph *p;
    int line;
    TCrossRef *crossRefPtr;

    paraOffset = 0;
    curOffset = 0;
    oldOffset = 0;
    line = 0;
    crossRefPtr = crossRefs + i;
    offset = crossRefPtr->offset;
    p = paragraphs;
    while (paraOffset + curOffset < offset)
        {
        char lbuf[256];

        oldOffset = paraOffset + curOffset;
        wrapText(p->text, p->size, curOffset, p->wrap, lbuf, sizeof(lbuf));
        ++line;
        if (curOffset >= p->size)
            {
            paraOffset += p->size;
            p = p->next;
            curOffset = 0;
            }
        }
    loc.x = offset - oldOffset - 1;
    loc.y = line;
    length = crossRefPtr->length;
    ref = crossRefPtr->ref;
}

char *THelpTopic::getLine( int line, char *buffer, int buflen )
{
    int offset, i;
    TParagraph *p;

    if (lastLine < line)
        {
        i = line;
        line -= lastLine;
        lastLine = i;
        offset = lastOffset;
        p = lastParagraph;
        }
    else
        {
        p = paragraphs;
        offset = 0;
        lastLine = line;
        }
    buffer[0] = 0;
    while (p != 0)
    {
        while (offset < p->size)
        {
            char lbuf[256];

            --line;
            strncpy(buffer, wrapText(p->text, p->size, offset, p->wrap, lbuf, sizeof(lbuf)), buflen);
            if (line == 0)
                {
                lastOffset = offset;
                lastParagraph = p;
                return buffer;
                }
        }
        p = p->next;
        offset = 0;
    }
    buffer[0] = 0;
    return buffer;
}

int THelpTopic::getNumCrossRefs()
{
    return numRefs;
}

int THelpTopic::numLines()
{
    int offset, lines;
    TParagraph *p;

    offset = 0;
    lines = 0;
    p = paragraphs;
    while (p != 0)
        {
        offset = 0;
        while (offset < p->size)
            {
            char lbuf[256];
            ++lines;
            wrapText(p->text, p->size, offset, p->wrap, lbuf, sizeof(lbuf));
            }
        p = p->next;
        }
    return lines;
}

void THelpTopic::setCrossRef( int i, TCrossRef& ref )
{
    TCrossRef *crossRefPtr;

    if (i < numRefs)
        {
        crossRefPtr = crossRefs + i;
        *crossRefPtr = ref;
        }
}


void THelpTopic::setNumCrossRefs( int i )
{
    TCrossRef  *p, *crossRefPtr;

    if (numRefs == i)
        return;
    p = new TCrossRef[i];
    if (numRefs > 0)
        {
        crossRefPtr = crossRefs;
        if (i > numRefs)
            memmove(p, crossRefPtr, numRefs * sizeof(TCrossRef));
        else
            memmove(p, crossRefPtr, i * sizeof(TCrossRef));

        delete [numRefs] crossRefPtr;
        }
    crossRefs = p;
    numRefs = i;
}


void THelpTopic::setWidth( int aWidth )
{
    width = aWidth;
}

void THelpTopic::writeParagraphs( opstream& s )
{
    int i;
    TParagraph  *p;
    int temp;

    p = paragraphs;
    for (i = 0; p != 0; ++i)
        p = p->next;
    s << i;
    for(p = paragraphs; p != 0; p = p->next)
        {
        s << p->size;
        temp = int(p->wrap);
        s << temp;
        s.writeBytes(p->text, p->size);
        }
}


void THelpTopic::writeCrossRefs( opstream& s )
{
    int i;
    TCrossRef *crossRefPtr;

    s << numRefs;
    if (crossRefHandler == notAssigned)
        {
        for(i = 0; i < numRefs; ++i)
            {
            crossRefPtr = crossRefs + i;
            s << crossRefPtr->ref << crossRefPtr->offset << crossRefPtr->length;
            }
        }
    else
        for (i = 0; i < numRefs; ++i)
            {
            crossRefPtr = crossRefs + i;
            (*crossRefHandler)(s, crossRefPtr->ref);
            s << crossRefPtr->offset << crossRefPtr->length;
            }
}

Boolean isBlank( char ch )
{
    if (isspace((uchar)ch))
        return True;
    else
        return False;
}

int scan( char *p, int offset, char c)
{
    char *temp1, *temp2;

    temp1 = p + offset;
    temp2 = strchr(temp1, c);
    if (temp2 == 0)
       return 256;
    else
       {
       if ((int)(temp2 - temp1) <= 256 )
         return (int) (temp2 - temp1) + 1;
       else
         return 256;
       }
}

void textToLine( void *text, int offset, int length, char *line )
{
    strncpy(line, (char *)text+offset, length);
    line[length] = 0;
}

char *THelpTopic::wrapText( char *text, int size, int& offset, Boolean wrap,
                            char *lineBuf, int lineBufLen )
{
    int i;

    i = scan(text, offset, '\n');
    if (i + offset > size )
        i = size - offset;
    if ((i >= width) && (wrap == True))
        {
        i = offset + width;
        if (i > size)
            i = size;
        else
            {
            while((i > offset) && !(isBlank(text[i])))
                --i;
/*
            if (i == offset)
                i = offset + width;
            else
                ++i;
*/
            if( i == offset )
                {
                i = offset + width;
                while( (i < size) && !isBlank(text[i]) )
                    ++i;
                if( i < size )
                    ++i;
                }
            else
                ++i;
            }
        if (i == offset)
            i = offset + width;
        i -= offset;
        }
    textToLine(text, offset, min(i,lineBufLen), lineBuf);
    if (lineBuf[min(strlen(lineBuf) - 1, lineBufLen)] == '\n')
        lineBuf[min(strlen(lineBuf) - 1, lineBufLen)] = 0;
    offset += min(i,lineBufLen);
    return lineBuf;
}

// THelpIndex

const char * const _NEAR THelpIndex::name = "THelpIndex";

void THelpIndex::write( opstream& os )
{
    long *indexArrayPtr;

    os << size;
    for (int i = 0; i < size; ++i)
        {
        indexArrayPtr = index + i;
        os << *indexArrayPtr;
        }
}

void *THelpIndex::read( ipstream& is )
{
    long *indexArrayPtr;

    is >> size;
    if (size == 0)
        index = 0;
    else
        {
        index =  new long[size];
        for(int i = 0; i < size; ++i)
            {
            indexArrayPtr = index + i;
            is >> *indexArrayPtr;
            }
        }
    return this;
}

TStreamable *THelpIndex::build()
{
    return new THelpIndex( streamableInit );
}

TStreamableClass RHelpIndex( THelpIndex::name,
                                  THelpIndex::build,
                                  __DELTA(THelpIndex)
                            );

THelpIndex::~THelpIndex()
{
    delete [size] index;
}


THelpIndex::THelpIndex(void): TObject ()
{
    size = 0;
    index = 0;
}

long THelpIndex::position(int i)
{
    long *indexArrayPtr;

    if (i < size)
        {
        indexArrayPtr = index + i;
        return (*indexArrayPtr);
        }
    else
        return -1;
}

void THelpIndex::add( int i, long val )
{
    int delta = 10;
    long *p;
    int newSize;
    long *indexArrayPtr;

    if (i >= size)
        {
        newSize = (i + delta) / delta * delta;
        p = new long[newSize];
        if (p != 0)
            {
            memmove(p, index, size * sizeof(long));
            memset(p+size, 0xFF, (newSize - size) * sizeof(long));
            }
        if (size > 0)
            {
            delete [size] index;
            }
        index = p;
        size = newSize;
        }
    indexArrayPtr = index + i;
    *indexArrayPtr = val;
}

// THelpFile

THelpFile::THelpFile( fpstream&  s )
{
    long magic;
    int handle;
    long size;

    magic = 0;
    s.seekg(0);
    handle = s.rdbuf()->fd();
    size = filelength(handle);
    s.seekg(0);
    if (size > sizeof(magic))
        s >> magic;
    if (magic != magicHeader)
        {
        indexPos = 12;
        s.seekg(indexPos);
        index =  new THelpIndex;
        modified = True;
        }
    else
        {
        s.seekg(8);
        s >> indexPos;
        s.seekg(indexPos);
        s >> index;
        modified = False;
        }
    stream = &s;
}

THelpFile::~THelpFile(void)
{
    long magic, size;
    int handle;

    if (modified == True)
        {
        stream->seekp(indexPos);
        *stream << index;
        stream->seekp(0);
        magic = magicHeader;
        handle = stream->rdbuf()->fd();
//
// note: at this time, a bug in filelength leaves the seek pointer at
//       the end of file, so we must save and restore the seek pointer
//       around the call; this can be removed when filelength is fixed.
//
  streampos sp=stream->tellp();
        size = filelength(handle) - 8;
  stream->seekp(sp);
        *stream << magic;
        *stream << size;
        *stream << indexPos;
        }
    delete stream;
    delete index;
}

THelpTopic *THelpFile::getTopic( int i )
{
    long pos;
    THelpTopic *topic = 0;

    pos = index->position(i);
    if (pos > 0 )
        {
        stream->seekg(pos);
        *stream >> topic;
        return topic;
        }
    else return(invalidTopic());
}

THelpTopic *THelpFile::invalidTopic()
{
    THelpTopic *topic;
    TParagraph *para;

    topic =  new THelpTopic;
    para =  new TParagraph;
    para->text = newStr(invalidContext);
    para->size = strlen(invalidContext);
    para->wrap = False;
    para->next = 0;
    topic->addParagraph(para);
    return topic;
}

void THelpFile::recordPositionInIndex( int i )
{
    index->add(i, indexPos);
    modified = True;
}

void THelpFile::putTopic( THelpTopic *topic )
{
    stream->seekp(indexPos);
    *stream << topic;
    indexPos = stream->tellp();
    modified = True;
}

void notAssigned( opstream& , int )
{
}


