// ter_mfc.cpp : implementation file
//

#include "stdafx.h"

#include "ter_mfc.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CTer

IMPLEMENT_DYNAMIC(CTer, CWnd)

BEGIN_MESSAGE_MAP(CTer, CWnd)
        //{{AFX_MSG_MAP(CTer)
                // NOTE - the ClassWizard will add and remove mapping macros here.
        //}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// Contruction 

CTer::CTer()
{
    // Variable initialization
    m_hBuffer=0;
    m_BufLen=0;
}

CTer::~CTer()
{
    DestroyWindow();
}

BOOL CTer::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
    LoadTerControl();

    return CWnd::Create(TER_CLASS, "", dwStyle ? dwStyle : DefaultEditStyles(), rect, pParentWnd, nID);
}


// Specify address where mfc should store the address for the window process
WNDPROC* CTer::GetSuperWndProcAddr()
{
    static WNDPROC NEAR pfnSuper;
    return &pfnSuper;
}

// Call TER menu option
void CTer::TerMenu(WPARAM MenuOption)
{
    if (MenuEnable(MenuOption)) TerWindowProc(WM_COMMAND,MenuOption,0L);
}

// Call TER window process
LRESULT CTer::TerWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
    WNDPROC pfnWndProc = *GetSuperWndProcAddr();
    #ifdef STRICT
        return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);
    #else
        return ::CallWindowProc((FARPROC)pfnWndProc, m_hWnd, nMsg, wParam, lParam);
    #endif
}

// Return edit styles to be used with the window
DWORD CTer::DefaultEditStyles()
{
    return WS_CHILD|WS_VISIBLE|WS_BORDER|TER_WORD_WRAP|TER_VSCROLL|TER_BORDER_MARGIN;  // control style
}

/////////////////////////////////////////////////////////////////////////////
// File io functions
void CTer::DeleteContents()
{
    HGLOBAL hMem;
    LPSTR pMem;

    ASSERT_VALID(this);
    ASSERT(m_hWnd!=NULL);

    // allocate an empty buffer
    hMem=::GlobalAlloc(GMEM_MOVEABLE,1);
    pMem=(LPSTR)::GlobalLock(hMem);
    pMem[0]=0xD;                         // line delimiter
    ::GlobalUnlock(hMem);
    
    SetHandle(hMem,1,NULL,TRUE);

    ASSERT_VALID(this);
}

DWORD CTer::GetBuffer()
        // Retrieve the text buffer from the control
{
    if (m_hBuffer) ::GlobalFree(m_hBuffer);

    m_hBuffer=GetHandle((int far *)&m_BufLen);
    return m_BufLen;
}

// Read and write CTer object to archive, with a 4 byte length prefix.
void CTer::Serialize(CArchive& ar)
{
    ASSERT_VALID(this);
    ASSERT(m_hWnd != NULL);

    if (ar.IsStoring()) {        // Save data
        DWORD nLen = GetBuffer();
        ar << nLen;              // write length
        WriteArchive(ar);
    }
    else {                       // load data
        DWORD dwLen;
        ar >> dwLen;             // read length
        ReadArchive(ar, dwLen);
    }
    ASSERT_VALID(this);
}

// Read certain amount of text from the file, assume at least nLen
// bytes are in the file.
void CTer::ReadArchive(CArchive& ar, DWORD nLen)
{
    ASSERT_VALID(this);
    
    HGLOBAL hBuf=::GlobalAlloc(GMEM_MOVEABLE, nLen+1);
    char * pMem=(char *)GlobalLock(hBuf);

    if (pMem == NULL) {
        AfxThrowMemoryException();
        ASSERT(FALSE);
    }
    
    // read the buffer
    DWORD BlockSize=0x4000;       // 16 blocks
    DWORD BytesDone=0;            // bytes written so far

    while (BytesDone<nLen) {
       if ((BytesDone+BlockSize)>nLen) BlockSize=nLen-BytesDone;

       if (ar.Read(&(pMem[BytesDone]),(UINT)BlockSize)!=(UINT)BlockSize) {
          ::GlobalUnlock(hBuf);
          AfxThrowArchiveException(CArchiveException::endOfFile);
          ASSERT(FALSE);
       }
       BytesDone+=BlockSize;
    }
    
    // pass the handle to the control
    pMem[nLen]=0;
    ::GlobalUnlock(hBuf);
    SetHandle(hBuf,nLen,"",TRUE);  // control now owns the handle

    ASSERT_VALID(this);
}

// Write just the text to an archive, no length prefix.
void CTer::WriteArchive(CArchive& ar)
{
    ASSERT_VALID(this);
    ASSERT(m_hBuffer!=NULL);

    char *pMem = (char *)::GlobalLock(m_hBuffer);

    ASSERT(pMem != NULL);

    // direct write for a huge buffer
    TRY
    {
       DWORD BlockSize=0x4000;       // 16 blocks
       DWORD BytesDone=0;            // bytes written so far

       while (BytesDone<m_BufLen) {
          if ((BytesDone+BlockSize)>m_BufLen) BlockSize=m_BufLen-BytesDone;
          ar.Write(&(pMem[BytesDone]),(UINT)BlockSize);
          BytesDone+=BlockSize;
       }
    }
    CATCH_ALL(e)
    {
        ::GlobalUnlock(m_hBuffer);
        THROW_LAST();
        ASSERT(FALSE);
    }
    END_CATCH_ALL
    
    // release our buffer
    ::GlobalUnlock(m_hBuffer);
    ::GlobalFree(m_hBuffer);
    m_hBuffer=0;
    m_BufLen=0;
    
    // mark the window as updated
    SetModify(FALSE);

    ASSERT_VALID(this);
}

void CTer::SerializeRaw(CArchive& ar)
    // Read/Write object as stand-alone file.
{
    ASSERT_VALID(this);
    if (ar.IsStoring())  {   // save data
        GetBuffer();         // retrieve data from control
        WriteArchive(ar);
    }
    else {                   // load file
        CFile *pFile = ar.GetFile();
        ASSERT(pFile->GetPosition() == 0);
        DWORD nFileSize = pFile->GetLength();
        ReadArchive(ar,nFileSize);
    }
    ASSERT_VALID(this);
}

///////////////////////////////////////////////////////////////////////////
// Menu Commands
// These functions simply invoke the given menu option
//////////////////////////////////////////////////////////////////////////
// Check if a menu item should be enabled
BOOL CTer::MenuEnable(int MenuItem)
{
    if (m_hWnd && MF_ENABLED==::TerMenuEnable(m_hWnd,MenuItem)) return TRUE;
    else                                                        return FALSE;

}

// Check if a menu item should be selected
int CTer::MenuSelect(int MenuItem)
{
    if (m_hWnd && MF_CHECKED==::TerMenuSelect(m_hWnd,MenuItem)) return 1;
    else                                                        return 0;
}


/////////////////////////////////////////////////////////////////////////////
// CTer API functions

// Get the handle to the control data
HGLOBAL CTer::GetHandle(int far *BufferLen) {return GetTerBuffer(m_hWnd,BufferLen);}

// Set new text to the control 
BOOL CTer::SetHandle(HGLOBAL hBuffer, int BufferLen, LPSTR title, BOOL release) {
   BOOL result;
   TerEnableRefresh(m_hWnd,FALSE);  // suppress painting by SetTerBuffer function
   result = SetTerBuffer(m_hWnd,hBuffer,BufferLen, (LPBYTE)title, release);
   TerEnableRefresh(m_hWnd,TRUE);  // resume painting
   return result;
}

void CTer::Cut()     {TerMenu(ID_CUT);}
void CTer::Copy()    {TerMenu(ID_COPY);}
void CTer::Paste()   {TerMenu(ID_PASTE);}
BOOL CTer::CanUndo() {return MenuEnable(ID_UNDO);}
BOOL CTer::Undo()    {TerMenu(ID_UNDO);return TRUE;}

// get the beginning and ending position of a highlighted block.
// EndPos points to the character immediately after the block
void CTer::GetSel(int &StartPos,int &EndPos)
{   
    StrTerField field;

    StartPos=EndPos=0;
    ::GetTerFields(m_hWnd,&field);

    if (field.HilightTypeW!=HILIGHT_OFF) {
       if (field.HilightTypeW==HILIGHT_LINE) {
          field.HilightBegColW=0;
          field.HilightEndColW=field.LineLenW;   // entire line highlighted
       }
       StartPos=TerRowColToAbs(m_hWnd,field.HilightBegRowW,field.HilightBegColW);
       EndPos=TerRowColToAbs(m_hWnd,field.HilightEndRowW,field.HilightEndColW);

       if (StartPos>EndPos) {                    // swap the values
           int temp=StartPos;
           StartPos=EndPos;
           EndPos=temp;
       }
    }
}

// Delete any highlighted block
void CTer::Clear()
{
    int StartPos,EndPos;

    GetSel(StartPos,EndPos);

    if (EndPos>0) TerMenu(ID_DEL);    // send the DEL key message
}

// Check if text modified
BOOL CTer::GetModify()
{
    StrTerField field;

    ::GetTerFields(m_hWnd,&field);

    return field.modified;
}

// set or reset modification status
void CTer::SetModify(BOOL bModified) {::TerSetModify(m_hWnd,bModified);}

// Get total number of lines the in the document
int CTer::GetLineCount()
{
    StrTerField field;

    ::GetTerFields(m_hWnd,&field);

    return field.TotalLinesW;
}

// retrieve the character index of the line.  Set nLine to -1 to specify 
// the current line.  The nLine is zero based parameter.
int CTer::LineIndex(int nLine)
{
    if (nLine<0) {            // get the current line
       StrTerField field;
       ::GetTerFields(m_hWnd,&field);
       nLine=field.CurLineW;
    }

    return ::TerRowColToAbs(m_hWnd,nLine,0);
}

// set or reset modification status
BOOL CTer::SetReadOnly(BOOL bReadOnly) {return ::TerSetReadOnly(m_hWnd,bReadOnly);}

// Retrieve the specified line.
int CTer:: GetLine(int index, LPSTR buffer)
{
   WORD MaxLength;

   MaxLength=*((LPWORD)buffer);  // retrive max bytes to copy
   return GetLine(index,buffer,(int)MaxLength);
}

int CTer::GetLine(int index, LPSTR buffer, int MaxLength)
{
   char string[301];
   int  LineLen,i;

   *((LPWORD)buffer)=(WORD)MaxLength; // store maximum number of bytes to copy

   if ((LineLen=::GetTerLine(m_hWnd,index,(LPBYTE)string,NULL))<=0) return 0;

   if (LineLen>MaxLength) LineLen=MaxLength;

   for (i=0;i<LineLen;i++) buffer[2+i]=string[i];  // first  word reserved for line length

   return LineLen;
}

// get the line index of the first line on the screen
int  CTer::GetFirstVisibleLine()
{
    StrTerField field;

    ::GetTerFields(m_hWnd,&field);

    return field.BeginLineW;
}

// get the line number (0 based) from the character index (0 based).
// when nIndex is -1, the number of the line that contains the first character
// of the selection is returned.  If there is no selection, the current line
// number is returned.
int  CTer::LineFromChar(int nIndex)
{
    int line;
    int  col;
    
    if (nIndex<0) {
       StrTerField field;
       ::GetTerFields(m_hWnd,&field);

       if (field.HilightTypeW!=HILIGHT_OFF) return field.HilightBegRowW;
       else                                 return field.CurLineW;
    }

    TerAbsToRowCol(m_hWnd,nIndex,&line,&col);

    return line;
}

// Get the length of the line which contains a character at an index
// equal to nLine. if nLine is -1, the function returns total number of 
// unselected lines in the first and last highlighted line.
int   CTer::LineLength(int nLine) 
{
    if (nLine>=0) return ::GetTerLine(m_hWnd,nLine, NULL,NULL); // get the length of the specified line

    // test if a block highlighted
    StrTerField field;
    ::GetTerFields(m_hWnd,&field);

    if (field.HilightTypeW==HILIGHT_OFF) return field.LineLenW;  // return the length of the current line

    // count the un-highlighted characters in the first and last line
    if (field.HilightTypeW==HILIGHT_LINE) return 0;              // entire lines highlighted

    int EndLineLen=::GetTerLine(m_hWnd,field.HilightEndRowW,NULL,NULL);

    return field.HilightBegColW+EndLineLen-field.HilightEndColW;

}

// Scroll specified number of lines in the vertical or horizontal direction
void CTer::LineScroll(int nLines, int nChars)
{
    StrTerField field;
    int i;
    WORD wParam;

    // disable painting
    ::GetTerFields(m_hWnd,&field);
    field.PaintEnabledW=FALSE;
    ::SetTerFields(m_hWnd,&field);

    // scroll vertically
    if (nLines) {
       wParam=SB_LINEDOWN;       // send line down message
       if (nLines<0) {
          wParam=SB_LINEUP;
          nLines=-nLines;
       }
       for (i=0;i<nLines;i++) TerWindowProc(WM_VSCROLL,wParam,0L);
    }

    if (nChars) {
       wParam=SB_LINEDOWN;       // send line down message
       if (nChars<0) {
          wParam=SB_LINEUP;
          nChars=-nChars;
       }
       for (i=0;i<nChars;i++) TerWindowProc(WM_HSCROLL,wParam,0L);
    }

    // enable painting
    ::GetTerFields(m_hWnd,&field);
    field.PaintEnabledW=TRUE;
    ::SetTerFields(m_hWnd,&field);
}

// Replace the exiting highlighted text by the specified text.  If a block
// is not highlighted, the new text is inserted at the current cursor location.
void CTer::ReplaceSel(char *NewText)
{
    StrTerField field;

    // Delete the exiting text
    ::GetTerFields(m_hWnd,&field);
    if (field.HilightTypeW!=HILIGHT_OFF) {
       ::SetTerCursorPos(m_hWnd,field.HilightBegRowW, field.HilightBegColW, FALSE);
       TerMenu(ID_DEL);       // send the DEL key message
    }
     
    InsertTerText(m_hWnd,(LPBYTE)NewText,TRUE);

}

// Highlight a block of text.  if nStartChar is 0 and nEndChar is -1,
// the entire text is highlighted.  If nStartChar is -1, any current
// highlighting is turned off.

void CTer::SetSel(int nStartChar, int nEndChar, BOOL bNoScroll)
{
   if (nStartChar==-1) {                // remove any highlighting
      ::DeselectTerText(m_hWnd,TRUE);
      return;
   }

   if (nStartChar==0 && nEndChar==-1) { // select the entire text
       StrTerField field;
       ::GetTerFields(m_hWnd,&field);
       int len=::GetTerLine(m_hWnd,field.TotalLinesW-1,NULL,NULL); // length of the last line
       ::SelectTerText(m_hWnd,0,0,field.TotalLinesW-1,len,bNoScroll);

       if (!bNoScroll) {                // set the cursor at the end of the block
          int LineLen=::GetTerLine(m_hWnd,field.TotalLinesW-1,NULL,NULL);
          if (field.WordWrapW && LineLen>0) LineLen--;
          ::SetTerCursorPos(m_hWnd,field.TotalLinesW-1, LineLen, TRUE);
       }
       return;
   }

   // select the specified line
   ::SelectTerText(m_hWnd,nStartChar,-1,nEndChar+1,-1,bNoScroll);
   if (!bNoScroll) {   // set the cursor at the end of the block
      int line;
      int  col;
      TerAbsToRowCol(m_hWnd,nEndChar+1,&line,&col);
      ::SetTerCursorPos(m_hWnd,line, col, TRUE);
   }

}
