/*  txtfls.C   */
/*  Copyright 1990 Mountain Math Software  */
/*  All Rights Reserved                    */

#include <fcntl.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sysintfc.h"
#include "sysconst.h"
#include "cgidbg.h"
#include "newaloc.h"
#include "usrprompt.h"
// #include "winmgr.h"
// #include "textwin.h"
#include "flstatus.h"
// #include "winpres.h"
#include "wfiles.h"
#include "txtfls.h"

#define TW(a,b)

/*
 * static void TW(const char * Where,TextWindow * Win)
 * {
 *	LogOut << "In " << Where << "TheWindow = 0x"
 *		<< hex((long) Win) << "\n" ;
 *	TestAlloc(Where);
 * }
 */

TextWindowWrite TextFile::Write(const char * str)
{
	TW("Write str",TheWindow);
	TextWindowWrite Return ;
	while (*str) Return = Write(*str++) ;
	return Return ;
}

TextWindowWrite TextFile::Write(char ch)
{
	// LogForm("TextFile::Write::%c", ch);
	TextWindowWrite Return = TextOk ;
	// LogMsg("After SetUnread");
	int WriteLineFlag = 0;
	if (!isprint(ch)) {
		switch (ch) {
case '\n' :
case '\r' :
			Return = TextLineWrap ;
			WriteLineFlag = 1 ;
			break ;
case '\t' :
			int Rem = NextXCharacter - ((NextXCharacter>>3)<<3);
			int Blanks = 8 - Rem ;
			if (!Blanks) Blanks = 8;
			if (NextXCharacter + Blanks < CharX)
				for (int i = 0 ; i < Blanks; i++) Write(' ') ;
			else WriteLineFlag = 1 ;
			break ;
default:
			TheLog << "*********TextWindowPreserve::Write:illegal char 0x"
				<< hex << (int) ch << dec << "\n" ;
		}
		ch = 0;
	} else if (NextXCharacter < CharX)  {
		LineBuf[NextXCharacter++] = ch ;
		if (NextXCharacter == CharX -1) Return =
			TextLineAboutToWrap ;
		ch = 0;
	} else WriteLineFlag = 1 ;
	if (WriteLineFlag) {
		LineBuf[NextXCharacter++] = '\n' ;
		CurrentFile->DoWriteLine(NextXCharacter,LineBuf);
		NextXCharacter = 0;
		Return = TextLineWrap ;
	}
	if (ch) LineBuf[NextXCharacter++] = ch ;
	return Return ;
}



TextFile::TextFile(TextWindow * window):
	SampleRate(1.),
	TheWindow(window),
	CurrentLine(0)
{
	CurrentFile = new FileStatus(this);
	InitSaveFileName = new char[MaxNameSize+1] ;
	SaveFileName = InitSaveFileName ;
	for (int i = 0 ; i < ReadBufSize; i++) Buffer[i] = '\0'  ;
	
	BytesLeft = 0 ;
	BasePtr = 0 ;
	LineBuf = 0;
	LineBufSize = 0;
	NextXCharacter= 0 ;
		// these are initialized in ::ScrollLines
		// they canot be set here because XCharacterPositions 
		// not yet defined
	CharY = CharX = -1;  // Indicates window has never been set and no check
		     // is needed

}

TextFile::~TextFile()
{
	delete LineBuf ;
	delete CurrentFile ;
	delete[] (void *) SaveFileName;
}


void TextFile::UpdateLine(int32 LineIndex,char * Line)
{
	TW("UpdateLine",TheWindow);
	for (int i = 0 ; i < CurrentFile->NextChangedLine ; i++) {
		int32 Res = CurrentFile->Updates[i].Line ;
		if (Res == UpdateEnd) break ;
		if (Res == UpdateIgnore) continue ;
		if (Res == LineIndex)
			CurrentFile->Updates[i].Line = UpdateIgnore ;
	}
	DeleteTrailingSpaces(Line) ;
	int16 DataSize ;
	int Size = (DataSize=strlen(Line)+1) + sizeof(int16) + sizeof(int32) ;
	if (CurrentFile->NextChangedLine >= CurrentFile->MaxNumberChangedLines
		|| (CurrentFile->ChangedBufSize -
		CurrentFile->NextFreeByteAtHead) < Size) {
			UpdateFile() ; 
			return ;
	}
	MoveNBytes(LineBuf,(char *)&LineIndex,sizeof(LineIndex));
	char * Wrt = LineBuf + sizeof(LineIndex) ;
	MoveNBytes(Wrt,(char *)&DataSize,sizeof(DataSize));
	Wrt += sizeof(DataSize);
	MoveNBytes(Wrt,Line,DataSize-1);
	Wrt[DataSize-1] = '\n' ;
	CurrentFile->SeekHead();
	CurrentFile->Updates[CurrentFile->NextChangedLine].Address =
		CurrentFile->NextFreeByteAtHead ;
	// CurrentFile->CheckWrite(Wrt,DataSize);
	if (write(CurrentFile->TheFile,LineBuf,Size) != Size)
		DbgError("TextFile::UpdateLine","bad write");
	CurrentFile->NextByteToRead += Size ;
	CurrentFile->NextFreeByteAtHead  += Size ;
	CurrentFile->Updates[CurrentFile->NextChangedLine++].Line = LineIndex ;
	CurrentFile->SeekEnd();
}

int TextFile::ReadLine(int32 LineToRead)
{
	TW("ReadLine",TheWindow);
	if (LineToRead != -1 ) {
	  if (LineToRead >= CurrentFile->NextWriteLineInFile) return -1 ;
	  int32 BaseLine = LineToRead / 10 ;
	  long BaseCharacter = CurrentFile->FileLines[BaseLine] ;
	  BaseLine *= 10 ;
	  if (lseek(CurrentFile->TheFile,CurrentFile->NextByteToRead =
		BaseCharacter,0) != CurrentFile->NextByteToRead)
		DbgError("TextFile::ReadLine", "bad seek");
	  BytesLeft = 0;
	  BasePtr = LineBuf ; 

	  // Loop to get to start of first line to read
	  int BytesRead = 0 ;
	  while (BaseLine < LineToRead) {
		  BytesLeft = BytesRead = read(CurrentFile->TheFile,Buffer,
			ReadBufSize);
		  if (BytesRead < 0) DbgError("TextFile::ReadLine", "bad read");
		  if (!BytesRead) return 0;
		  CurrentFile->NextByteToRead += BytesRead ;
		  BasePtr = Buffer ;
		  while (BasePtr < Buffer + BytesRead) {
			  BytesLeft-- ;
			  if (*BasePtr++ == '\n')
				  if (++BaseLine >= LineToRead) break ;
		  }
	  }
	}

	// read line
	char * Dest = LineBuf ;
	for (;;) {
		while(BytesLeft) {
			BytesLeft--;
			if (*BasePtr == '\n') break ;
		    	else *Dest++ = *BasePtr++ ;
		}
		if (*BasePtr == '\n') break ;
/*
 *		LogOut << "Reading new buffer, NextByteToRead = " << 
 *			CurrentFile->NextByteToRead << "\n" ;
 */
		BytesLeft = read(CurrentFile->TheFile,Buffer,ReadBufSize);
		if (BytesLeft < 0) DbgError("TextFile::ReadLine", "bad read 2");
		if (!BytesLeft) return 0;
		CurrentFile->NextByteToRead += BytesLeft ;
		BasePtr = Buffer ;
	}

	BasePtr++;
	*Dest++ = '\0';
	return Dest - LineBuf ;
}

void TextFile::ReadLines(int32 LineToRead, char ** Destination,
	LineStateOptions *State, int NumberLines)
{
	TW("ReadLines",TheWindow);
	int TheLineToRead = LineToRead ;
	for (int LinesRead = 0 ; LinesRead < NumberLines ;LinesRead++) {
		if (LineToRead + LinesRead>=CurrentFile->NextWriteLineInFile)
			break ;
		int LineSize = ReadLine(TheLineToRead ) ;
		TheLineToRead = -1 ; // continue reading from same buffer
		if (LineSize < 0) DbgError("TextFile::ReadLines","bad read");
		char * Dest = Destination[LinesRead] ;
		char * EndOfLine = Dest+CharX ;
		for (int i = 0 ; i < LineSize;i++) *Dest++ = LineBuf[i] ;
		while (Dest < EndOfLine) *Dest++ = '\0';
		if (LineSize) State[LinesRead] = LineStateInFile ;
		else State[LinesRead] = LineStateNewEmpty ;
	}
	for(;LinesRead < NumberLines;LinesRead++) {
		char * Dest = Destination[LinesRead] ;
		char * EndOfLine = Dest+CharX ;
		while (Dest < EndOfLine) *Dest++ = '\0';
		State[LinesRead] = LineStateNewEmpty ;
	}
}


void TextFile::ReadUpdatedLines(int LineToRead,char ** Lines,int Number)
{
	TW("ReadUpdatedLines",TheWindow);
	for (int i = 0 ; i < CurrentFile->NextChangedLine ; i++) {
		int32 Res = CurrentFile->Updates[i].Line ;
		if (Res == UpdateEnd) break ;
		if (Res == UpdateIgnore) continue ;
		if (Res >= LineToRead && Res < LineToRead + Number) {
			char * Dest=Lines[Res-LineToRead];
			char * dbgsv = Dest ;
			int16 Length = CurrentFile->ReadUpdateLine(
				CurrentFile->Updates[i].Address, Dest);
			char * EndOfLine = Dest+CharX ;
			Dest += Length ;
			while (Dest < EndOfLine) *Dest++ = '\0' ;
		}
	}
}


void TextFile::ReadScrolledLines(int Number, int32 LineToRead)
{
/*
 *	TW("ReadScrolledLines",TheWindow);
 *	if (!TheWindow) return ;
 *	char ** DestBase = TheWindow->CurrentLines ;
 *	if (!DestBase) return ;
 *	LineStateOptions * State = TheWindow->LineState ;
 *	if (Number < 0) {
 *		Number = -Number ;
 *		DestBase += CharY - Number ;
 *		State += CharY - Number ;
 *	} 
 *	ReadLines(LineToRead,DestBase,State,Number);
 *	ReadUpdatedLines(LineToRead,DestBase,Number);
 */
}

void TextFile::Init()
{
	TW("Init",TheWindow);
	if (CharX > -1) NewCheck() ;
	else {
		// CharX = TheWindow->GetXCharacterPositions();
		// LogOut << "CharX = " << CharX << "\n" ;
	}
	// CharY = TheWindow->YCharacterPositions ;
	if (!LineBuf) {
		LineBufSize = CharX + sizeof(int16) + sizeof(int32) ;
		LineBuf = new char [LineBufSize] ;
		for (int i = 0 ; i < LineBufSize;i++) LineBuf[i] = '\0';
	}
/*	TextWindowPreserve * Pres = TheWindow->GetPreserve() ;
 *	if (CurrentFile->NextWriteLineInFile && Pres) {
 *		int OffScreen = Pres->GetNewYCursor() - CharY + 1 ;
 *
 *		LogOut << "CurrentLine = " << CurrentLine <<
 *			", OffScreen = " << OffScreen << "\n" ;
 *
 *		if (OffScreen > 0) {
 *			CurrentLine += OffScreen ;
 *			Pres->SetYCursor(Pres->GetNewYCursor() - OffScreen) ;
 *		}
 *
 *		LogOut << "Init::ReadScrolledLines(" << CharY << ", "
 *			<< CurrentLine << ")\n" ;
 *
 *		ReadScrolledLines(CharY,CurrentLine);
 *	}
 */
}


void TextFile::NewCheck()
{
	TW("NewCheck",TheWindow);
/*
 *	if (CharX > TheWindow->GetXCharacterPositions())
 *		DbgError("TextFile::NewWindow","new window size error");
 */
}

void TextFile::NewWindow(TextWindow * Win)
{
	// LogOut << "InNewWindow Win = " << hex((long) Win) << "\n" ;
	TheWindow = Win ;
	Init();
}

void TextFile::RemoveWindow()
{
	TW("RemoveWindow",TheWindow);
	if (CharY > 1) UpdateScrolledLines(CharY);
	TheWindow = 0;
	CharY = 0 ;
 	NextXCharacter = 0 ;
	if (LineBuf) for (int i = 0 ; i < CharX ; i++) LineBuf[i] = '\0' ;
}


static void SaveFileCallBack(const char * Resp, InputType, void * Object)
{
	if (strcmp(Resp,"yes")) {
		*Output + OutputHelp << "File save aborted.\n" ;
		return ;
	}
	((TextFile *) Object)->DoSaveFileCompletion();
}

void TextFile::DoSaveFile(const char * Name)
{
	TW("DoSaveFile",TheWindow);
	if (!TheWindow) {
		TheLog << "*************TextFile::DoSaveFile: No Window\n";
		return;
	}
	// if (!*Name) Name = TheWindow->GetName() ;
	if (strlen(Name) > MaxNameSize) {
		*Output + OutputHelp << "File name `" << Name <<
			"' is too long.\n" ; 
		SaveAborted();
	}
	strcpy(InitSaveFileName,Name);
/*
 *	LogOut << "TextFile::DoSaveFile - InitSaveFileName = `" <<
 *		SaveFileName << "'\n." ;
 */
	PromptStatus Status = PrepareToWriteFile(Name,SaveFileCallBack,
		this,"saving window to disk");
	if (Status != PromptStatusOk) SaveAborted();
}

static int strange(int, int x)
{
	return close(x);
}

void TextFile::DoSaveFileCompletion()
{
	const char * Name = SaveFileName ;
	// LogOut<<"TextFile::DoSaveFileCompletion - Name = `"<<Name << "'\n.";
	int TheFile = open(Name, O_WRONLY | O_CREAT | O_TRUNC, OpenFileMode) ;
	if (TheFile < 1) {
		// TheWindowsManager->SuspendRedraw(OutputHelp);
		*Output + OutputHelp << "Can't create file `" << Name << "'.\n";
		SystemErrorMessage();
		// TheWindowsManager->ClearSuspendRedraw(OutputHelp);
		SaveAborted();
		return ;
	}
	UpdateScrolledLines(CharY);
	UpdateFile(TheFile) ;
	int dum = 0 ;
	strange (dum,TheFile) ;
	// close (TheFile) ;
}

static void DummyGetFileName(const char * String,enum InputType,
	void * TheFileToSave)
	// TextFile * TheFileToSave)
{
	if (!TheFileToSave) {
		TheLog << "******DummyGetFileName No File\n";
		return ;
	}
	((TextFile *) TheFileToSave)->DoSaveFile(String);
}

void TextFile::SaveFile()
{
	TW("SaveFile",TheWindow);
	if (!TheWindow) {
		TheLog << "*************TextFile::SaveFile: No Window\n";
		return;
	}
	
	UserPrompt * Prompt = UserPromptSave(
		"Enter file name or RETURN for default (", DummyGetFileName,
		this);
/*
 *	PromptStatus Status = TheWindowsManager->PromptUser(Prompt,
 *		"saving window to disk", TheWindow->GetName(),"):" );
 *	if (Status != PromptStatusOk) SaveAborted();
 */
}

void TextFile::SaveAborted()
{
/*
 *	*Output + OutputHelp <<
 *		"File save of `" << TheWindow->GetName() <<"' aborted.\n" ;
 */
}

int TextFile::UpdateScrolledLines(int Number)
{
	// LogOut << "UpdateScrolledLines(" << Number << ")\n" ;
	TW("UpdateScrolledLines",TheWindow);
	if (!TheWindow) return 0 ;
	// Number is negative for scrolling up
	// This routine updates file and fills in new text
	if (CurrentLine + Number <  0) {
		Number = -CurrentLine ;
		if (Number > -1) return 0 ;
	}
	char ** LinesToCheck ;
	int NumberLines = Number ;
	// LinesToCheck = TheWindow->CurrentLines ;
	if (Number < 0)  NumberLines = CharY ;
		// in scrolling down we need to update every line
		// to keep file record continuous
	else NumberLines = Number ;
	return Number ;
/*
 *	for (int i = 0 ; i < NumberLines ; i ++) {
 *	   switch (TheWindow->LineState[i]) {
 * case LineStateNewEmpty:
 *		continue ;
 * case LineStateNewWritten:
 *		CurrentFile->WriteLine(CurrentLine+i,LinesToCheck[i]);
 *		TheWindow->LineState[i] = LineStateInFile ;
 *		continue ;
 * case LineStateInFile:
 *		continue ;
 * case LineStateChangedFromFile:
 *		UpdateLine(CurrentLine+i,LinesToCheck[i]);
 *		TheWindow->LineState[i] = LineStateInFile ;
 *		continue ;
 *	  }
 *	}
 *	// LogOut << "Exit TextFile::UpdateScrolledLined\n" ;
 *	return Number ;
 */
}

int TextFile::ScrollLines(int Number)
{
	TW("ScrollLines",TheWindow);
	int ActNumber = UpdateScrolledLines(Number) ;
	if (ActNumber) {
		int Offset  = 0;
		if (ActNumber > 0) Offset = CharY - ActNumber ;
		CurrentLine += ActNumber ;
		ReadScrolledLines(ActNumber,CurrentLine+Offset);
	}
	return ActNumber;
}

static int CompareLineNumbers(void *AA, void * BB)
// static int CompareLineNumbers(ChangedLines *AA, ChangedLines * BB)
{
	int32 A = ((ChangedLines *) AA)->Line;
	if (A < 0) return 1; 
		// put all negative values at end
	int32 B = ((ChangedLines *) BB)->Line;
	if (A < B) return -1 ;
	if (A == B) return 0;
	return 1 ;
}



void TextFile::UpdateFileFromDisplay()
{
	if (CharY > 0) UpdateScrolledLines(CharY);
}

void TextFile::UpdateFile(int CopyFile)
{
	TW("UpdateFile",TheWindow);
	// Sort record or file updates in line number order
	qsort ((char *) CurrentFile->Updates, CurrentFile->NextChangedLine,
		sizeof(CurrentFile->Updates[0]), CompareLineNumbers) ;
	
	// Create new file and initialize it
	FileStatus * NewFile = new FileStatus(this) ;

	// transfer data one line at a time checking first to see
	// if a line is updated by either the lines at beginning of old file
	// or by lines in CurrentLines

	int NextChangedLine = 0;
	int NextEditingLine = CurrentLine ;
	int EditingLineIndex = 0;
	

	int32 EndOfWrite = CurrentFile->NextWriteLineInFile ;

	int32 EndOfEdit = CurrentLine + CharY ;
	if (TheWindow) {
		int Overhang = EndOfEdit - EndOfWrite ;

/*
 *		if (Overhang> 0)
 *			for (int j = CharY - Overhang ; j < CharY ; j++)
 *				if (TheWindow->LineState[j] !=
 *					LineStateNewEmpty)
 *					EndOfWrite= CurrentLine + j ;
 */
	} else EndOfEdit = -1 ;

	int TheLineToRead = 0;
	for (int32 i = 0 ;i < EndOfWrite; i++ ) {
		int Length = 0 ;
		if (i < CurrentFile->NextWriteLineInFile) Length =
			ReadLine(TheLineToRead) ;
		TheLineToRead = -1;  // continue reading from same buffer
		if (Length < 0) DbgError("TextFile::UpdateFile","bad read");
		if ( i == CurrentFile->Updates[NextChangedLine].Line) {
			if (i != NextEditingLine) {
				long SaveLoc = CurrentFile->NextByteToRead ;
				Length =CurrentFile->ReadUpdateLine(
				  CurrentFile->Updates[NextChangedLine].Address,
					LineBuf);
				if(lseek(CurrentFile->TheFile,
					CurrentFile->NextByteToRead=SaveLoc,0)
					!= CurrentFile->NextByteToRead)
					DbgError( "TextFile::UpdateFile",
					"seek error");
			}
			if (NextChangedLine < CurrentFile->NextChangedLine-1)
				NextChangedLine++;
		}
/*
 *		if ( i == NextEditingLine) {
 *			char * TheLine =
 *				TheWindow->CurrentLines[EditingLineIndex];
 *			TheWindow->LineState[EditingLineIndex++] =
 *				LineStateInFile ;
 *			if (++NextEditingLine >= EndOfEdit)
 *				NextEditingLine= -1 ;
 *			DeleteTrailingSpaces(TheLine);
 *			Length = strlen(TheLine);
 *			strcpy(LineBuf,TheLine);
 *		}
 */
		LineBuf[Length++] = '\n' ;
		NewFile->DoWriteLine(Length,LineBuf) ;
		if (CopyFile) write(CopyFile,LineBuf,Length);
	}

	// make new file the current file and delete the old file

	delete CurrentFile ;
	CurrentFile = NewFile ;
}

void TextFile::Page(int Lines)
{
	TW("Page",TheWindow);
	int Ret = ScrollLines(Lines);
	int Diff = Ret - Lines ;
	// if (Diff) if (TheWindow) TheWindow->Rotate(Diff) ;
}

void TextFile::PageStart()
{
	TW("PageStart",TheWindow);
	UpdateScrolledLines(CharY);
	CurrentLine = 0;
	ReadScrolledLines(CharY,CurrentLine);
}

int TextFile::PageEnd()
{
	// Move Cursor to end of current file
	// End of current file may be part of the current display.
	// At Exit the cursor should be at the last line in display
	// Unless there are not enoguth total lines to fille the
	// display (less the last line)
	// LogOut << "TextFile::PageEnd\n" ;
	int End = CurrentFile->NextWriteLineInFile ;
	int YCursor = 0;
	if (!TheWindow) return 0;
	// YCursor = TheWindow->YCursorPosition ;
	int HighestLine = -1;
/*
 *	for (int i = 0 ; i < CharY; i++) 
 *		if (TheWindow->LineState[i] != LineStateNewEmpty)
 *			HighestLine = i;
 */
	HighestLine++ ;	
	int NewEnd = CurrentLine + HighestLine ;
	if (NewEnd < End) NewEnd = End ;

	if (HighestLine <= YCursor)
		if (CurrentLine + YCursor >= NewEnd) return YCursor ;
	UpdateScrolledLines(CharY) ;

	
	int NewCursor = CharY -1 ;
	if (NewCursor > NewEnd) NewCursor = NewEnd ;


	CurrentLine = NewEnd - NewCursor ;


	if (CurrentLine < 0) CurrentLine = 0;

	ReadScrolledLines(NewCursor+1,CurrentLine);
	return NewCursor ;
}

void TextFile::PageUp()
{
	TW("PageUp",TheWindow);
	Page(-CharY);
}


void TextFile::PageDown()
{
	TW("PageDown",TheWindow);
	Page(CharY);
}

void TextFile::GoToAddress(double Address)
{
	if (Address > CurrentLine) PageEnd();
	UpdateScrolledLines(CharY);
	CurrentLine = (int32) Address;
	ReadScrolledLines(CharY,CurrentLine);
}

int TextFile::GetNumberLines()
{
	UpdateScrolledLines(CharY);
	return CurrentFile->NextWriteLineInFile ;
}

double TextFile::GetSampleRate()
{
	// must be expanded to allow sample rate for dynamic windows
	return SampleRate ;
}

double TextFile::GetCurrentAddress()
{
	return CurrentLine ;
}

void TextFile::DisplayAddress(const char * Name, int YCursor)
{
/*
 *	int32 End = GetNumberLines();
 *	*Output + TheWindowsManager->HelpOrDialogue() << "`" << Name <<
 *		"' is at line " << CurrentLine << " ( " <<
 *		CurrentLine - End ;
 *	if (YCursor) *Output << "), cursor is at " << CurrentLine + YCursor 
 *		<< " ( " << CurrentLine + YCursor - End ;
 *	*Output << ") of " << End << ".\n" ;
 */
}

int TextFile::IsEndOfFileInDisplay()
{
	if (!CurrentFile) DbgError("TextFile::IsEndOfFileInDisplay", "no file");
	int32 FileEnd = CurrentFile->GetFileEnd() ;
	int Return = CurrentLine + CharY > FileEnd ;
/*
 * 	if (Return) LogOut << "IsEndOfFileInDisplay, CurrentLine = " <<
 *		CurrentLine << ", CharY = " << CharY << ", Last = " <<
 *		FileEnd << "\n" ;
 */

	return Return ;
}

void TextFile::AppendTextEnd(const char * Str)
{
	// LogOut << "TextFile::AppendTextEnd\n" ;
	UpdateFileFromDisplay();
	// UpdateScrolledLines(CharY);
	// LogOut << "After UpdateScrolledLines\n" ;
	Write(Str);
	// LogOut << "TextFile::AppendTextEnd exit\n" ;
}

double TextFile::GetEndTime()
{
	return GetNumberLines();
}

void DeleteTrailingSpaces(char * Str)
{
	for (char * Ptr = Str+strlen(Str) - 1; Ptr >=Str ; Ptr--)
		if(isgraph(*Ptr)) return;
	else *Ptr = '\0' ;
}

