/******************************************************************************

  This file is part of the:

  TCEditor class by SET, copyright (c) 1996.

    This files can be used by Robert Hhne for your RHIDE, any other use
  needs the permission of the author.

  E-Mail: salvador@inti.edu.ar
  
  Telephone: (+541) 759-0013
  
  Postal Address:
  Salvador E. Tropea
  Curapalige 2124
  (1678) Caseros - 3 de Febrero
  Prov: Buenos Aires
  Argentina

******************************************************************************/

// That's the first include because is used to configure the editor.
#include "ceditint.h"

#define Uses_TGroup
#define Uses_TEvent
#define Uses_opstream
#define Uses_ipstream
#define Uses_TStreamableClass
#define Uses_TStringCollection
#define Uses_TFileDialog
#define Uses_TProgram
#define Uses_TDeskTop
#define Uses_MsgBox
#include <tv.h>

#define Uses_TCEditor
#define Uses_TCFileEditor
#include "ceditor.h"

#include <limits.h>
#include <string.h>
#include <fstream.h>
#include <fcntl.h>
#ifdef __GNUC__
#include <unistd.h>
#else
#include <io.h>
#include <malloc.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

#ifndef __GNUC__
#define MAXLFN 256
#define IOSBin  ios::binary
#else
#define IOSBin  ios::bin
#endif

inline uint32 min( uint32 u1, uint32 u2 )
{
    return u1 < u2 ? u1 : u2;
}

TCFileEditor::TCFileEditor( const TRect& bounds,
                          TScrollBar *aHScrollBar,
                          TScrollBar *aVScrollBar,
                          TIndicator *aIndicator,
                          const char *aFileName
                        ) :
    TCEditor( bounds, aHScrollBar, aVScrollBar, aIndicator, 4096 )
{
    if( aFileName == 0 )
        fileName[0] = EOS;
    else
        {
        strcpy( fileName, aFileName );
        if( isValid )
            isValid = loadFile();
        }
}

void TCFileEditor::doneBuffer()
{
    delete buffer;
}


static void readBlock(TCEditor *editor)
{
  char fname[PATH_MAX];
  strcpy(fname,"*.*");
  TFileDialog *d = new TFileDialog("*.*", _("Read from file"),
       _("~N~ame"), fdOpenButton, 100 );
  d->setData(fname);
  if ( TProgram::deskTop->execView( d ) != cmCancel )
    {
     if (editor->IslineInEdition)
        editor->MakeEfectiveLineInEdition();
     // Save the cursor pos
     uint32 c = (uint32)(editor->ColToPointer()-editor->buffer);
     //  Create a new editor for this file, this is made in this way to get
     // an automatic conversion for UNIX files.
     d->getData(fname);
     TCFileEditor *feditor = new TCFileEditor(TRect(0,0,1,1),NULL,NULL,NULL,fname);
     // If non-persistent blocks kill the selection
     if (!editor->PersistentBlocks && editor->hasSelection())
        editor->clipCut();
     editor->lock();
     editor->selHided=False;
     // Insert all the new editor
     editor->insertBuffer(feditor->buffer,0,feditor->bufLen,True,True);
     // Destroy the temporal editor
     delete feditor;
     // Put the cursor in the original position
     editor->GotoOffSet(c);
     editor->trackCursor(False);
     // Update the view
     editor->unlock();
     editor->update(ufView);
    }
  delete d;
}

static void writeBlock(TCEditor *editor)
{
  char fname[PATH_MAX];
  TFileDialog *d;
  if (editor->selHided || !editor->hasSelection())
     return;

  strcpy(fname,"*.*");
  d = new TFileDialog("*.*", _("Write to file"),_("~N~ame"), fdOpenButton, 100 );
  d->setData(fname);
  if ( TProgram::deskTop->execView( d ) != cmCancel )
  {
    FILE *f;
    d->getData(fname);
    delete d;
    f = fopen(fname,"rb");
    if (f)
    {
      fclose(f);
      if (messageBox(mfYesButton | mfNoButton | mfCancelButton | mfWarning,
                     _("File %s exist. Overwrite ?"),fname) != cmYes) return;
    }
    f = fopen(fname,"w+b");
    if (editor->IslineInEdition)
       editor->MakeEfectiveLineInEdition();
    fwrite(editor->buffer+editor->selStart,1,editor->selEnd-editor->selStart,f);
    fclose(f);
    return;
  }
  delete d;
}


void TCFileEditor::handleEvent( TEvent& event )
{
    TCEditor::handleEvent(event);
    switch( event.what )
        {
        case evCommand:
            switch( event.message.command )
                {
                case cmSave:
                     save();
                     break;

                case cmSaveAs:
                     saveAs();
                     break;

                case cmcReadBlock:
                     readBlock(this);
                     break;

                case cmcWriteBlock:
                     writeBlock(this);
                     break;

                case cmcSaveAsUNIX:
                     saveAsUNIX();
                     break;

                default:
                    return;
                }
            break;
        default:
            return;
        }
    clearEvent(event);
}

void TCFileEditor::initBuffer()
{
    buffer = new char[bufSize];
}

#undef CHECK_FOR_ONLY_LF
#ifdef __DJGPP__
#define CHECK_FOR_ONLY_LF
#endif

Boolean TCFileEditor::loadFile()
{
#ifdef CHECK_FOR_ONLY_LF
    int i,crfound=0;
    char tmp[PATH_MAX];
#endif
    FILE *f = fopen(fileName,"rb");
    if( !f )
        {
        setBufLen( 0 );
        return True;
        }
    else
        {
#ifdef CHECK_FOR_ONLY_LF
/* check for a unix text file (is a heuristic, because only 1024 chars checked */
        {
          char tmpbuf[1024];
          memset(tmpbuf,0,1024);
          long fsize = filelength( fileno(f) );
          if (fsize > 1024) fsize = 1024;
          fread( tmpbuf, fsize, 1, f);
          for (i=0;i<fsize;i++)
          {
            if (tmpbuf[i] == 13)
               crfound = 1;
            else
               if (tmpbuf[i] == 10)
                  break;
          }
          if (crfound)
          {
            fseek(f,0,SEEK_SET);
          }
          else
          {
            /* This uses the feature of DJGPP to convert automatically
               LF's to CR/LF's when writing in TEXT-mode */
            FILE *ftemp;
            fseek(f,0,SEEK_SET);
            tmpnam(tmp);
            ftemp = fopen(tmp,"w+t");
            while ((fsize = fread(tmpbuf,1,1024,f)) > 0)
              fwrite(tmpbuf,1,fsize,ftemp);
            fclose(ftemp);
            fclose(f);
            f = fopen(tmp,"rb");
          }
        }
#endif
        unsigned long fSize = filelength( fileno(f) );
        if( !setBufSize(fSize) )
            {
            editorDialog( edOutOfMemory );
#ifdef CHECK_FOR_ONLY_LF
            if (!crfound) remove(tmp);
#endif
            return False;
            }
        else
            {
            unsigned long real_read;
            /* On linux you can (like I) mount an MS-DOS filesystem with
               conv=auto, which converts CR/LF to LF. In that case
               real_read < fSize */
            real_read = fread( buffer, 1, fSize, f );
            fclose(f);
            if( real_read == 0 )
                {
                editorDialog( edReadError, fileName );
#ifdef CHECK_FOR_ONLY_LF
                if (!crfound) remove(tmp);
#endif
                return False;
                }
            else
                {
                setBufLen(real_read);
#ifdef CHECK_FOR_ONLY_LF
                if (!crfound) remove(tmp);
#endif
                return True;
                }
            }
        }
}

Boolean TCFileEditor::save()
{
    if( *fileName == EOS )
        return saveAs();
    else
        return saveFile();
}

Boolean TCFileEditor::saveAs()
{
    Boolean res = False;
    if( editorDialog( edSaveAs, fileName ) != cmCancel )
        {
        fexpand( fileName );
        message( owner, evBroadcast, cmcUpdateTitle, 0 );
        res = saveFile();
        if( isClipboard() )
            *fileName = EOS;
        }
    return res;
}

Boolean TCFileEditor::saveAsUNIX()
{
 Boolean res = False;
 if ( editorDialog( edSaveAs, fileName ) != cmCancel )
   {
    fexpand( fileName );
    message( owner, evBroadcast, cmcUpdateTitle, 0 );
    res = saveFile(True);
    if( isClipboard() )
        *fileName = EOS;
   }
 return res;
}

static void writeBlock( ofstream& f, char *buf, unsigned len )
{
    while( len > 0 )
        {
        int l = len < INT_MAX ? len : INT_MAX;
        f.write( buf, l );
        buf += l;
        len -= l;
        }
}



Boolean TCFileEditor::saveFile(Boolean Unix)
{

 if ( (editorFlags & efBackupFiles) != 0 )
   {
    char *dot,*slash;
    int flen = strlen(fileName);
    char backupName[PATH_MAX];
    strcpy(backupName,fileName);
    dot = strrchr(backupName,'.');
    slash = strrchr(backupName,'/');
    if (dot < slash) // directory has a dot but not the filename
      dot = NULL;
    if (!dot)
      dot = backupName + flen;
    strcpy(dot,backupExt);
    unlink( backupName );
    rename( fileName, backupName );
   }

 ofstream f( fileName, ios::out | IOSBin );

 if ( !f )
   {
    editorDialog( edCreateError, fileName );
    return False;
   }
 else
   {
    if (IslineInEdition)
       MakeEfectiveLineInEdition();

    if (!Unix)
       writeBlock( f, buffer, bufLen );
    else
      {
       unsigned y;
       char *s=(char *)alloca(limit.x+4);
       if (!s)
         {
          editorDialog( edOutOfMemory );
          return False;
         }
       char *cur=buffer;
       int l;

       for (y=0; y<=totalLines; y++)
          {
           l=lenLines[y];
           memcpy(s,cur,l);
           cur+=l;
           if (s[l-1]=='\n')
             {
              s[l-2]='\n';
              l--;
             }
           else
             {
              s[l]='\n';
             }
           f.write(s,l);
          }
      }

    if ( !f )
      {
       editorDialog( edWriteError, fileName );
       return False;
      }
    else
      {
       struct stat s;
       char aux[PATH_MAX+30];

       modified = False;
       update(ufUpdate);
       f.close();
       if (stat(fileName,&s)==0)
          sprintf(aux,_("Saved: %s (%ld bytes)."),fileName,s.st_size);
       else
          sprintf(aux,_("Stat failed."));
       setStatusLine(aux);
      }
   }
 return True;
}

Boolean TCFileEditor::setBufSize( uint32 newSize )
{
   newSize = (uint32)((newSize + 0x1000) & 0xFFFFF000L);
    if( newSize != bufSize )
       {
        // Make all in a way that allow a fail without loosing the data
        unsigned DeltaCl=(unsigned)(curLinePtr-buffer);
        char *temp;
        if ( (temp = new char[newSize]) == 0 )
          {
           delete temp;
           editorDialog( edOutOfMemory );
           return False;
          }
        memcpy( temp, buffer, min( newSize, bufSize ) );
        delete buffer;
        buffer=temp;
        bufSize = newSize;
        curLinePtr=buffer+DeltaCl;
       }
    return True;
}

void TCFileEditor::shutDown()
{
    setCmdState(cmSave, False);
    setCmdState(cmSaveAs, False);
    TCEditor::shutDown();
}

void TCFileEditor::updateCommands()
{
    TCEditor::updateCommands();
    setCmdState(cmSave, True);
    setCmdState(cmSaveAs, True);
}

Boolean TCFileEditor::valid( ushort command )
{
    if( command == cmValid )
        return isValid;
    else
        {
        if( modified == True )
            {
            int d;
            if( *fileName == EOS )
                d = edSaveUntitled;
            else
                d = edSaveModify;

            switch( editorDialog( d, fileName ) )
                {
                case cmYes:
                    return save();
                case cmNo:
                    modified = False;
                    return True;
                case cmCancel:
                    return False;
                }
            }
        }
    return True;
}

void TCFileEditor::write( opstream& os )
{
    TCEditor::write( os );
    os.writeString( fileName );
#ifndef SAVE_ALL_VARS
    os << selStart << selEnd << (unsigned)(ColToPointer()-buffer);
#else
    os << selStart << selEnd   // The selected area
       << curPos.x << curPos.y // The real position, a pointer can point to
                               // a non existing position but the cursor can
                               // be in a X position where there is no text.
       << (uchar)selHided      // Needed to know if the selection is hided or not.
                               // The mode of edition
       << (uchar)SyntaxHL
    ;
    uint16 flags=CompactFlags();
    os << flags << tabSize;

    // Markers
    os.writeBytes(Markers,sizeof(Markers));

    // From v0.2.11
    os << Xr1 << Yr1 << Xr2 << Yr2 << (uchar)selRectHided;

    // From v0.2.13
    os << (uchar)staticNoMoveToEndPaste;
#endif
}

void *TCFileEditor::read( ipstream& is )
{
    TCEditor::read( is );
    is.readString( fileName, sizeof( fileName ) );
    if( isValid )
        {
#ifndef SAVE_ALL_VARS
        uint32 sStart, sEnd, curs;
        is >> sStart >> sEnd >> curs;
        //TurnOnCHighLight();
        //LineMeassure=LineMeassureC;
        SetHighlightTo(shlCSyntax);
        isValid = loadFile();
        if ( isValid && sEnd <= bufLen )
          {
           GotoOffSet(curs);
           selStart=sStart;
           selEnd=sEnd;
           trackCursor( True );
           update(ufView);
          }
#else
        uint32 sStart, sEnd;
        int X,Y;
        uchar sh,shl;
        uint16 flags;
        is >> sStart >> sEnd >> X >> Y >> sh >> shl;
        is >> flags >> tabSize;

        ExpandFlags(flags,False);
        SetHighlightTo((shlState)shl);
        isValid = loadFile();

        // Markers after the loadFile because loadFile cleans the markers
        is.readBytes(Markers,sizeof(Markers));

        if (LoadingVersion>=0x211)
          {
           char aux;
           is >> Xr1 >> Yr1 >> Xr2 >> Yr2 >> aux;
           selRectHided=Boolean(aux);
          }

        if (LoadingVersion>=0x213)
          {
           char aux;
           is >> aux;
           staticNoMoveToEndPaste=Boolean(aux);
          }

        if (isValid)
          {
           MoveCursorTo(X,Y);
           if (X>limit.x)
              limit.x=X+1;
           selHided=(sh) ? True : False;
           if ( sEnd <= bufLen )
             {
              selStart=sStart;
              selEnd=sEnd;
             }
           trackCursor( True );
           update(ufView);
          }
#endif
        }
    return this;
}

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

TCFileEditor::TCFileEditor( StreamableInit ) : TCEditor( streamableInit )
{
}

const char * near TCFileEditor::backupExt=".bkp";
#if !defined( RHIDE ) // in RHIDE they are defined in other files
const char * const near TCFileEditor::name = "TCFileEditor";
#endif

