/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_l10ntools.hxx"
#include <stdio.h>
#include <tools/fsys.hxx>
#include <tools/stream.hxx>
#include <tools/list.hxx>

// local includes
#include "tagtest.hxx"
#include "gsicheck.hxx"

#define MAX_GID_LID_LEN 250

/*****************************************************************************/
void PrintMessage( ByteString aType, ByteString aMsg, ByteString aPrefix,
	ByteString aContext, sal_Bool bPrintContext, sal_uLong nLine, ByteString aUniqueId = ByteString() )
/*****************************************************************************/
{
	fprintf( stdout, "%s %s, Line %lu", aType.GetBuffer(), aPrefix.GetBuffer(), nLine );
	if ( aUniqueId.Len() )
		fprintf( stdout, ", UniqueID %s", aUniqueId.GetBuffer() );
	fprintf( stdout, ": %s", aMsg.GetBuffer() );

	if ( bPrintContext )
		fprintf( stdout, "  \"%s\"", aContext.GetBuffer() );
	fprintf( stdout, "\n" );
}

/*****************************************************************************/
void PrintError( ByteString aMsg, ByteString aPrefix,
	ByteString aContext, sal_Bool bPrintContext, sal_uLong nLine, ByteString aUniqueId = ByteString() )
/*****************************************************************************/
{
    PrintMessage( "Error:", aMsg, aPrefix, aContext, bPrintContext, nLine, aUniqueId );
}

sal_Bool LanguageOK( ByteString aLang )
{
    if ( !aLang.Len() )
        return sal_False;

    if ( aLang.IsNumericAscii() )
        return sal_True;

    if ( aLang.GetTokenCount( '-' ) == 1 )
        return aLang.IsAlphaAscii() && aLang.IsLowerAscii();
    else if ( aLang.GetTokenCount( '-' ) == 2 )
    {
        ByteString aTok0( aLang.GetToken( 0, '-' ) );
        ByteString aTok1( aLang.GetToken( 1, '-' ) );
        return  aTok0.Len() && aTok0.IsAlphaAscii() && aTok0.IsLowerAscii()
             && aTok1.Len() && aTok1.IsAlphaAscii() && aTok1.IsUpperAscii()
             && !aTok1.EqualsIgnoreCaseAscii( aTok0 );
    }

    return sal_False;
}


//
// class LazySvFileStream
//


class LazySvFileStream : public SvFileStream
{

private:
    String aFileName;
    sal_Bool bOpened;
    StreamMode eOpenMode;

public:
    LazySvFileStream()
    : aFileName()
    , bOpened( sal_False )
    , eOpenMode( 0 )
    {};

    void SetOpenParams( const String& rFileName, StreamMode eOpenModeP )
    {
        aFileName = rFileName;
        eOpenMode = eOpenModeP;
    };

    void LazyOpen();
};

void LazySvFileStream::LazyOpen()
{
    if ( !bOpened )
    {
        Open( aFileName, eOpenMode );
	    if ( !IsOpen())
	    {
		    fprintf( stderr, "\nERROR: Could not open Output-File %s!\n\n", ByteString( aFileName, RTL_TEXTENCODING_ASCII_US ).GetBuffer() );
		    exit ( 4 );
	    }
        bOpened = sal_True;
    }
}


//
// class GSILine
//

/*****************************************************************************/
GSILine::GSILine( const ByteString &rLine, sal_uLong nLine )
/*****************************************************************************/
				: ByteString( rLine )
				, nLineNumber( nLine )
				, bOK( sal_True )
                , bFixed ( sal_False )
{
    if ( rLine.GetTokenCount( '\t' ) == 15 )
    {
        aFormat = FORMAT_SDF;
        aUniqId = rLine.GetToken( 0, '\t' );
        aUniqId.Append("/").Append( rLine.GetToken( 1, '\t' ) ).Append("/").Append( rLine.GetToken( 3, '\t' ) ).Append("/").Append( rLine.GetToken( 4, '\t' ) ).Append("/").Append( rLine.GetToken( 5, '\t' ) ).Append("/").Append( rLine.GetToken( 6, '\t' ) ).Append("/").Append( rLine.GetToken( 7, '\t' ) );
        aLineType = "";
        aLangId = rLine.GetToken( 9, '\t' );
        aText = rLine.GetToken( 10, '\t' );
        aQuickHelpText = rLine.GetToken( 12, '\t' );
        aTitle = rLine.GetToken( 13, '\t' );

        // do some more format checks here
        if ( !rLine.GetToken( 8, '\t' ).IsNumericAscii() )
        {
		    PrintError( "The length field does not contain a number!", "Line format", rLine.GetToken( 8, '\t' ), sal_True, GetLineNumber(), GetUniqId() );
		    NotOK();
        }
        if ( !LanguageOK( aLangId ) )
        {
		    PrintError( "The Language is invalid!", "Line format", aLangId, sal_True, GetLineNumber(), GetUniqId() );
		    NotOK();
        }
        // limit GID and LID to MAX_GID_LID_LEN chars each for database conformity, see #137575#
        if ( rLine.GetToken( 4, '\t' ).Len() > MAX_GID_LID_LEN || rLine.GetToken( 5, '\t' ).Len() > MAX_GID_LID_LEN )
        {
			PrintError( ByteString("GID and LID may only be ").Append( ByteString::CreateFromInt32(MAX_GID_LID_LEN) ).Append( " chars long each!" ), "Line format", aLangId, sal_True, GetLineNumber(), GetUniqId() );
		    NotOK();
        }
    }
    else    // allow tabs in gsi files
    {
        aFormat = FORMAT_GSI;
        ByteString sTmp( rLine );
        sal_uInt16 nPos = sTmp.Search( "($$)" );
        sal_uInt16 nStart = 0;
        if ( nPos != STRING_NOTFOUND )
        {
        	aUniqId = sTmp.Copy( nStart, nPos - nStart );
            nStart = nPos + 4;  // + length of the delemiter
            nPos = sTmp.Search( "($$)", nStart );
        }
        if ( nPos != STRING_NOTFOUND )
        {
        	aLineType = sTmp.Copy( nStart, nPos - nStart );
            nStart = nPos + 4;  // + length of the delemiter
            nPos = sTmp.Search( "($$)", nStart );
            aUniqId.Append( "/" );
            aUniqId.Append( aLineType );
        }
        if ( nPos != STRING_NOTFOUND )
        {
        	aLangId = sTmp.Copy( nStart, nPos - nStart );
            nStart = nPos + 4;  // + length of the delemiter
            nPos = sTmp.Search( "($$)", nStart );
        }
        if ( nPos != STRING_NOTFOUND )
        {
//        	ByteString aStatus = sTmp.Copy( nStart, nPos - nStart );     // ext int ...
            nStart = nPos + 4;  // + length of the delemiter
        }
        if ( nPos != STRING_NOTFOUND )
        	aText = sTmp.Copy( nStart );
        else
            aFormat = FORMAT_UNKNOWN;
    }

    if ( FORMAT_UNKNOWN == GetLineFormat() )
        NotOK();
}

/*****************************************************************************/
void GSILine::NotOK()
/*****************************************************************************/
{
    bOK = sal_False;
}

/*****************************************************************************/
void GSILine::ReassembleLine()
/*****************************************************************************/
{
    ByteString aReassemble;
    if ( GetLineFormat() == FORMAT_SDF )
    {
        sal_uInt16 i;
        for ( i = 0 ; i < 10 ; i++ )
        {
            aReassemble.Append( GetToken( i, '\t' ) );
            aReassemble.Append( "\t" );
        }
        aReassemble.Append( aText );
        aReassemble.Append( "\t" );
        aReassemble.Append( GetToken( 11, '\t' ) ); // should be empty but there are some places in sc. Not reflected to sources!!
        aReassemble.Append( "\t" );
        aReassemble.Append( aQuickHelpText );
        aReassemble.Append( "\t" );
        aReassemble.Append( aTitle );
        for ( i = 14 ; i < 15 ; i++ )
        {
            aReassemble.Append( "\t" );
            aReassemble.Append( GetToken( i, '\t' ) );
        }
        *(ByteString*)this = aReassemble;
    }
    else if ( GetLineFormat() == FORMAT_GSI )
    {
        sal_uInt16 nPos = Search( "($$)" );
        sal_uInt16 nStart = 0;
        if ( nPos != STRING_NOTFOUND )
        {
            nStart = nPos + 4;  // + length of the delemiter
            nPos = Search( "($$)", nStart );
        }
        if ( nPos != STRING_NOTFOUND )
        {
            nStart = nPos + 4;  // + length of the delemiter
            nPos = Search( "($$)", nStart );
        }
        if ( nPos != STRING_NOTFOUND )
        {
            nStart = nPos + 4;  // + length of the delemiter
            nPos = Search( "($$)", nStart );
        }
        if ( nPos != STRING_NOTFOUND )
        {
            nStart = nPos + 4;  // + length of the delemiter
        }
        if ( nPos != STRING_NOTFOUND )
        {
            aReassemble = Copy( 0, nStart );
            aReassemble += aText;
            *(ByteString*)this = aReassemble;
        }
        else
            PrintError( "Cannot reassemble GSI line (internal Error).", "Line format", "", sal_False, GetLineNumber(), GetUniqId() );
    }
    else 
        PrintError( "Cannot reassemble line of unknown type (internal Error).", "Line format", "", sal_False, GetLineNumber(), GetUniqId() );
}

//
// class GSIBlock
//
/*****************************************************************************/
GSIBlock::GSIBlock( sal_Bool PbPrintContext, sal_Bool bSource, sal_Bool bTrans, sal_Bool bRef, sal_Bool bAllowKID, sal_Bool bAllowSusp )
/*****************************************************************************/
            : pSourceLine( NULL )
            , pReferenceLine( NULL )
            , bPrintContext( PbPrintContext )
            , bCheckSourceLang( bSource )
            , bCheckTranslationLang( bTrans )
            , bReference( bRef )
            , bAllowKeyIDs( bAllowKID )
            , bAllowSuspicious( bAllowSusp )
            , bHasBlockError( sal_False )
{
}

/*****************************************************************************/
GSIBlock::~GSIBlock()
/*****************************************************************************/
{
	delete pSourceLine;
	delete pReferenceLine;

	for ( sal_uLong i = 0; i < Count(); i++ )
		delete ( GetObject( i ));
}

/*****************************************************************************/
void GSIBlock::InsertLine( GSILine* pLine, ByteString aSourceLang)
/*****************************************************************************/
{
	if ( pLine->GetLanguageId().Equals( aSourceLang ) )
    {
        if ( pSourceLine )
        {
            PrintError( "Source Language entry double. Treating as Translation.", "File format", "", pLine->GetLineNumber(), pLine->GetUniqId() );
            bHasBlockError = sal_True;
            pSourceLine->NotOK();
            pLine->NotOK();
        }
        else
        {
		    pSourceLine = pLine;
            return;
        }
    }
	sal_uLong nPos = 0;

    if ( aSourceLang.Len() ) // only check blockstructure if source lang is given
    {
		while ( nPos < Count() )
        {
            if ( GetObject( nPos )->GetLanguageId().Equals( pLine->GetLanguageId() ) )
            {
                PrintError( "Translation Language entry double. Checking both.", "File format", "", pLine->GetLineNumber(), pLine->GetUniqId() );
                bHasBlockError = sal_True;
                GetObject( nPos )->NotOK();
                pLine->NotOK();
            }
			nPos++;
        }
    }
	Insert( pLine, LIST_APPEND );
}

/*****************************************************************************/
void GSIBlock::SetReferenceLine( GSILine* pLine )
/*****************************************************************************/
{
    pReferenceLine = pLine;
}

/*****************************************************************************/
void GSIBlock::PrintMessage( ByteString aType, ByteString aMsg, ByteString aPrefix,
	ByteString aContext, sal_uLong nLine, ByteString aUniqueId )
/*****************************************************************************/
{
    ::PrintMessage( aType, aMsg, aPrefix, aContext, bPrintContext, nLine, aUniqueId );
}

/*****************************************************************************/
void GSIBlock::PrintError( ByteString aMsg, ByteString aPrefix,
	ByteString aContext, sal_uLong nLine, ByteString aUniqueId )
/*****************************************************************************/
{
    PrintMessage( "Error:", aMsg, aPrefix, aContext, nLine, aUniqueId );
}

/*****************************************************************************/
void GSIBlock::PrintList( ParserMessageList *pList, ByteString aPrefix,
	GSILine *pLine )
/*****************************************************************************/
{
	sal_uLong i;
	for ( i = 0 ; i < pList->Count() ; i++ )
	{
		ParserMessage *pMsg = pList->GetObject( i );
		ByteString aContext;
		if ( bPrintContext )
		{
			if ( pMsg->GetTagBegin() == STRING_NOTFOUND )
				aContext = pLine->GetText().Copy( 0, 300 );
			else
				aContext = pLine->Copy( pMsg->GetTagBegin()-150, 300 );
			aContext.EraseTrailingChars(' ');
			aContext.EraseLeadingChars(' ');
		}

        PrintMessage( pMsg->Prefix(), pMsg->GetErrorText(), aPrefix, aContext, pLine->GetLineNumber(), pLine->GetUniqId() );
	}
}

/*****************************************************************************/
sal_Bool GSIBlock::IsUTF8( const ByteString &aTestee, sal_Bool bFixTags, sal_uInt16 &nErrorPos, ByteString &aErrorMsg, sal_Bool &bHasBeenFixed, ByteString &aFixed ) const
/*****************************************************************************/
{
    String aUTF8Tester( aTestee, RTL_TEXTENCODING_UTF8 );
    if ( STRING_MATCH != (nErrorPos = ByteString( aUTF8Tester, RTL_TEXTENCODING_UTF8 ).Match( aTestee )) )
    {
        aUTF8Tester = String( aTestee.GetBuffer(), nErrorPos, RTL_TEXTENCODING_UTF8 );
        nErrorPos = aUTF8Tester.Len();
        aErrorMsg = ByteString( "UTF8 Encoding seems to be broken" );
        return sal_False;
    }

    nErrorPos = aUTF8Tester.SearchChar( String::CreateFromAscii( "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f"
                "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f" ).GetBuffer() );
    if ( nErrorPos != STRING_NOTFOUND )
    {
        aErrorMsg = ByteString( "String contains illegal character" );
        return sal_False;
    }

    if ( bFixTags )
    {
        bHasBeenFixed = sal_False;
        aFixed.Erase();
    }

    if ( !bAllowKeyIDs )
    {
        sal_Bool bIsKeyID = sal_False;
        sal_Bool bNewId = sal_False;
        ByteString aID( aTestee );
		sal_uInt16 nAfterID = 0;

		if ( aTestee.Equals( "{&", 0, 2 ) )
        {   // check for strings from instset_native like "{&Tahoma8}335795.Installation Wiza ..."
            sal_uInt16 nTagEnd = aTestee.Search( '}' );
            if ( nTagEnd != STRING_NOTFOUND )
            {
                if ( bFixTags )
                    aFixed = aTestee.Copy( 0, nTagEnd+1 );
                nErrorPos = nTagEnd+1;
                aID = aTestee.Copy( nTagEnd+1 );
				nAfterID = nTagEnd+1;
            }
        }

		ByteString aDelimiter( (String)String( sal_Unicode(0x2016) ), RTL_TEXTENCODING_UTF8 );

        if ( aID.Equals( aDelimiter, 6, aDelimiter.Len() ) )
        {   // New KeyId     6 Letters, digits and spechial chars followed by delimiter
            bNewId = sal_True;
            nErrorPos = 1;
            aID = aID.Copy( 0, 6 );
			nAfterID += 6;
			nAfterID = nAfterID + aDelimiter.Len();
        }
        else if ( ( aID.GetChar(6) == '*' ) && aID.Equals( aDelimiter, 7, aDelimiter.Len() ) )
        {   // New KeyId     6 Letters, digits and spechial chars followed by '*delimiter' to indicate translation in progress 
            bNewId = sal_True;
            nErrorPos = 1;
            aID = aID.Copy( 0, 6 );
			nAfterID += 7;
			nAfterID = nAfterID + aDelimiter.Len();
        }
        else if ( aID.GetTokenCount( '.' ) > 1 )
        {	// test for old KeyIDs       5 to 6 digits followed by a dot   '44373.'
            bNewId = sal_False;
            nErrorPos = 1;
            aID = aID.GetToken( 0, '.' );
			nAfterID = nAfterID + aID.Len();
        }
		else
		{
			aID.Erase();
		}

        if ( bNewId )
            {
                if ( aID.Len() == 6 )
                {
                    bIsKeyID = sal_True;
                    ByteString aDigits("0123456789abcdefghijklmnopqrstuvwxyz+-<=>");
                    for ( sal_uInt16 i=0 ; i < aID.Len() ;i++ )
                    {
                        if ( aDigits.Search( aID.GetChar(i) ) == STRING_NOTFOUND )
                            bIsKeyID = sal_False;
                    }
                }
            }
        else
        {
            if ( aID.Len() > 0 && aID.GetChar(aID.Len()-1) == '*' )
                aID.Erase( aID.Len()-1 );

            if ( aID.IsNumericAscii() && aID.Len() >= 5 )
                bIsKeyID = sal_True;
        }

        if ( bIsKeyID )
        {
            aErrorMsg = ByteString( "String contains KeyID" );
            if ( bFixTags )
            {
                aFixed += aTestee.Copy( nAfterID );
                bHasBeenFixed = sal_True;
                aErrorMsg = ByteString( "FIXED String containing KeyID" );
            }
            else
                aErrorMsg = ByteString( "String contains KeyID" );
            return sal_False;
        }
    }

    return sal_True;
}

/*****************************************************************************/
sal_Bool GSIBlock::TestUTF8( GSILine* pTestee, sal_Bool bFixTags )
/*****************************************************************************/
{
    sal_uInt16 nErrorPos = 0;
    ByteString aErrorMsg;
    sal_Bool bError = sal_False;
    ByteString aFixed;
    sal_Bool bHasBeenFixed = sal_False;
    if ( !IsUTF8( pTestee->GetText(), bFixTags, nErrorPos, aErrorMsg, bHasBeenFixed, aFixed ) )
    {
        ByteString aContext( pTestee->GetText().Copy( nErrorPos, 20 ) );
        PrintError( aErrorMsg.Append(" in Text at Position " ).Append( ByteString::CreateFromInt32( nErrorPos ) ), "Text format", aContext, pTestee->GetLineNumber(), pTestee->GetUniqId() );
        bError = sal_True;
        if ( bHasBeenFixed )
        {
            pTestee->SetText( aFixed );
            pTestee->SetFixed();
        }
    }
    if ( !IsUTF8( pTestee->GetQuickHelpText(), bFixTags, nErrorPos, aErrorMsg, bHasBeenFixed, aFixed ) )
    {
        ByteString aContext( pTestee->GetQuickHelpText().Copy( nErrorPos, 20 ) );
        PrintError( aErrorMsg.Append(" in QuickHelpText at Position " ).Append( ByteString::CreateFromInt32( nErrorPos ) ), "Text format", aContext, pTestee->GetLineNumber(), pTestee->GetUniqId() );
        bError = sal_True;
        if ( bHasBeenFixed )
        {
            pTestee->SetQuickHelpText( aFixed );
            pTestee->SetFixed();
        }
    }
    if ( !IsUTF8( pTestee->GetTitle(), bFixTags, nErrorPos, aErrorMsg, bHasBeenFixed, aFixed ) )
    {
        ByteString aContext( pTestee->GetTitle().Copy( nErrorPos, 20 ) );
        PrintError( aErrorMsg.Append(" in Title at Position " ).Append( ByteString::CreateFromInt32( nErrorPos ) ), "Text format", aContext, pTestee->GetLineNumber(), pTestee->GetUniqId() );
        bError = sal_True;
        if ( bHasBeenFixed )
        {
            pTestee->SetTitle( aFixed );
            pTestee->SetFixed();
        }
    }
    if ( bError )
        pTestee->NotOK();
    return !bError;
}


/*****************************************************************************/
sal_Bool GSIBlock::HasSuspiciousChars( GSILine* pTestee, GSILine* pSource )
/*****************************************************************************/
{
    sal_uInt16 nPos = 0;
    if ( !bAllowSuspicious && ( nPos = pTestee->GetText().Search("??")) != STRING_NOTFOUND )
        if ( pSource->GetText().Search("??") == STRING_NOTFOUND )
        {
            String aUTF8Tester = String( pTestee->GetText(), 0, nPos, RTL_TEXTENCODING_UTF8 );
            sal_uInt16 nErrorPos = aUTF8Tester.Len();
            ByteString aContext( pTestee->GetText().Copy( nPos, 20 ) );
            PrintError( ByteString("Found double questionmark in translation only. Looks like an encoding problem at Position " ).Append( ByteString::CreateFromInt32( nErrorPos ) ), "Text format", aContext, pTestee->GetLineNumber(), pTestee->GetUniqId() );
            pTestee->NotOK();
            return sal_True;
        }

    return sal_False;
}


/*****************************************************************************/
sal_Bool GSIBlock::CheckSyntax( sal_uLong nLine, sal_Bool bRequireSourceLine, sal_Bool bFixTags )
/*****************************************************************************/
{
	static LingTest aTester;
    sal_Bool bHasError = sal_False;

	if ( !pSourceLine )
	{
        if ( bRequireSourceLine )
        {
    		PrintError( "No source language entry defined!", "File format", "", nLine );
            bHasBlockError = sal_True;
        }
	}
	else
	{
        aTester.CheckReference( pSourceLine );
        if ( pSourceLine->HasMessages() )
		{
			PrintList( pSourceLine->GetMessageList(), "ReferenceString", pSourceLine );
			pSourceLine->NotOK();
            bHasError = sal_True;
		}
	}
    if ( bReference )
    {
        if ( !pReferenceLine )
        {
            GSILine *pSource;
            if ( pSourceLine )
                pSource = pSourceLine;
            else
                pSource = GetObject( 0 );   // get some other line
            if ( pSource )
                PrintError( "No reference line found. Entry is new in source file", "File format", "", pSource->GetLineNumber(), pSource->GetUniqId() );
            else
                PrintError( "No reference line found. Entry is new in source file", "File format", "", nLine );
            bHasBlockError = sal_True;
	    }
	    else
	    {
		    if ( pSourceLine && !pSourceLine->Equals( *pReferenceLine ) )
		    {
                xub_StrLen nPos = pSourceLine->Match( *pReferenceLine );
                ByteString aContext( pReferenceLine->Copy( nPos - 5, 15) );
                aContext.Append( "\" --> \"" ).Append( pSourceLine->Copy( nPos - 5, 15) );
                PrintError( "Source Language Entry has changed.", "File format", aContext, pSourceLine->GetLineNumber(), pSourceLine->GetUniqId() );
			    pSourceLine->NotOK();
                bHasError = sal_True;
		    }
	    }
    }

    if ( pSourceLine )
        bHasError |= !TestUTF8( pSourceLine, bFixTags );

	sal_uLong i;
	for ( i = 0; i < Count(); i++ )
	{
		aTester.CheckTestee( GetObject( i ), pSourceLine != NULL, bFixTags );
		if ( GetObject( i )->HasMessages() || aTester.HasCompareWarnings() )
        {
            if ( GetObject( i )->HasMessages() || aTester.GetCompareWarnings().HasErrors() )
			    GetObject( i )->NotOK();
            bHasError = sal_True;
			PrintList( GetObject( i )->GetMessageList(), "Translation", GetObject( i ) );
			PrintList( &(aTester.GetCompareWarnings()), "Translation Tag Missmatch", GetObject( i ) );
		}
        bHasError |= !TestUTF8( GetObject( i ), bFixTags );
        if ( pSourceLine )
            bHasError |= HasSuspiciousChars( GetObject( i ), pSourceLine );
	}

	return bHasError || bHasBlockError;
}

void GSIBlock::WriteError( LazySvFileStream &aErrOut, sal_Bool bRequireSourceLine  )
{
    if ( pSourceLine && pSourceLine->IsOK() && bCheckSourceLang && !bHasBlockError )
        return;

	sal_Bool bHasError = sal_False;
	sal_Bool bCopyAll = ( !pSourceLine && bRequireSourceLine ) || ( pSourceLine && !pSourceLine->IsOK() && !bCheckTranslationLang ) || bHasBlockError;
	sal_uLong i;
	for ( i = 0; i < Count(); i++ )
	{
		if ( !GetObject( i )->IsOK() || bCopyAll )
		{
			bHasError = sal_True;
            aErrOut.LazyOpen();
			aErrOut.WriteLine( *GetObject( i ) );
		}
	}

	if ( pSourceLine && ( bHasError || !pSourceLine->IsOK() ) && !( !bHasError && bCheckTranslationLang ) )
    {
        aErrOut.LazyOpen();
		aErrOut.WriteLine( *pSourceLine );
    }
}

void GSIBlock::WriteCorrect( LazySvFileStream &aOkOut, sal_Bool bRequireSourceLine )
{
	if ( ( !pSourceLine && bRequireSourceLine ) || ( pSourceLine && !pSourceLine->IsOK() && !bCheckTranslationLang ) )
		return;

	sal_Bool bHasOK = sal_False;
	sal_uLong i;
	for ( i = 0; i < Count(); i++ )
	{
		if ( ( GetObject( i )->IsOK() || bCheckSourceLang ) && !bHasBlockError )
		{
			bHasOK = sal_True;
            aOkOut.LazyOpen();
			aOkOut.WriteLine( *GetObject( i ) );
		}
	}

	if ( ( pSourceLine && pSourceLine->IsOK() && ( Count() || !bCheckTranslationLang ) ) || ( bHasOK && bCheckTranslationLang ) )
    {
        aOkOut.LazyOpen();
		aOkOut.WriteLine( *pSourceLine );
    }
}

void GSIBlock::WriteFixed( LazySvFileStream &aFixOut, sal_Bool /*bRequireSourceLine*/ )
{
    if ( pSourceLine && !pSourceLine->IsFixed() && bCheckSourceLang )
        return;

	sal_Bool bHasFixes = sal_False;
	sal_uLong i;
	for ( i = 0; i < Count(); i++ )
	{
		if ( GetObject( i )->IsFixed() )
		{
			bHasFixes = sal_True;
            aFixOut.LazyOpen();
			aFixOut.WriteLine( *GetObject( i ) );
		}
	}

	if ( pSourceLine && ( bHasFixes || pSourceLine->IsFixed() ) )
    {
        aFixOut.LazyOpen();
		aFixOut.WriteLine( *pSourceLine );
    }
}


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

/*****************************************************************************/
void Help()
/*****************************************************************************/
{
	fprintf( stdout, "\n" );
	fprintf( stdout, "gsicheck Version 1.9.0 (c)1999 - 2006 by SUN Microsystems\n" );
	fprintf( stdout, "=========================================================\n" );
	fprintf( stdout, "\n" );
	fprintf( stdout, "gsicheck checks the syntax of tags in GSI-Files and SDF-Files\n" );
	fprintf( stdout, "         checks for inconsistencies and malicious UTF8 encoding\n" );
	fprintf( stdout, "         checks tags in Online Help\n" );
	fprintf( stdout, "         checks for *new* KeyIDs and relax GID/LID length to %s\n", ByteString::CreateFromInt32(MAX_GID_LID_LEN).GetBuffer() );
	fprintf( stdout, "\n" );
	fprintf( stdout, "Syntax: gsicheck [ -c ] [-f] [ -we ] [ -wef ErrorFilename ] [ -wc ]\n" );
	fprintf( stdout, "                 [ -wcf CorrectFilename ] [ -s | -t ] [ -l LanguageID ]\n" );
	fprintf( stdout, "                 [ -r ReferenceFile ] filename\n" );
	fprintf( stdout, "\n" );
	fprintf( stdout, "-c    Add context to error message (Print the line containing the error)\n" );
	fprintf( stdout, "-f    try to fix errors. See also -wf -wff \n" );
	fprintf( stdout, "-wf   Write File containing all fixed parts\n" );
	fprintf( stdout, "-wff  Same as above but give own filename\n" );
	fprintf( stdout, "-we   Write File containing all errors\n" );
	fprintf( stdout, "-wef  Same as above but give own filename\n" );
	fprintf( stdout, "-wc   Write File containing all correct parts\n" );
	fprintf( stdout, "-wcf  Same as above but give own filename\n" );
	fprintf( stdout, "-s    Check only source language. Should be used before handing out to vendor.\n" );
	fprintf( stdout, "-t    Check only Translation language(s). Should be used before merging.\n" );
	fprintf( stdout, "-k    Allow KeyIDs to be present in strings\n" );
    fprintf( stdout, "-e    disable encoding checks. E.g.: double questionmark \'??\' which may be the\n" );
    fprintf( stdout, "      result of false conversions\n" );
	fprintf( stdout, "-l    ISO Languagecode or numerical 2 digits Identifier of the source language.\n" );
	fprintf( stdout, "      Default is en-US. Use \"\" (empty string) or 'none'\n" );
	fprintf( stdout, "      to disable source language dependent checks\n" );
	fprintf( stdout, "-r    Reference filename to check that source language entries\n" );
	fprintf( stdout, "      have not been changed\n" );
   	fprintf( stdout, "\n" );
}

/*****************************************************************************/
#if defined(UNX) || defined(OS2)
int main( int argc, char *argv[] )
#else
int _cdecl main( int argc, char *argv[] )
#endif
/*****************************************************************************/
{

	sal_Bool bError = sal_False;
	sal_Bool bPrintContext = sal_False;
	sal_Bool bCheckSourceLang = sal_False;
    sal_Bool bCheckTranslationLang = sal_False;
    sal_Bool bWriteError = sal_False;
	sal_Bool bWriteCorrect = sal_False;
    sal_Bool bWriteFixed = sal_False;
    sal_Bool bFixTags = sal_False;
    sal_Bool bAllowKID = sal_False;
    sal_Bool bAllowSuspicious = sal_False;
    String aErrorFilename;
	String aCorrectFilename;
    String aFixedFilename;
    sal_Bool bFileHasError = sal_False;
    ByteString aSourceLang( "en-US" );     // English is default
	ByteString aFilename;
    ByteString aReferenceFilename;
    sal_Bool bReferenceFile = sal_False;
	for ( sal_uInt16 i = 1 ; i < argc ; i++ )
	{
		if ( *argv[ i ] == '-' )
		{
			switch (*(argv[ i ]+1))
			{
				case 'c':bPrintContext = sal_True;
					break;
				case 'w':
					{
						if ( (*(argv[ i ]+2)) == 'e' )
                        {
                            if ( (*(argv[ i ]+3)) == 'f' )
                                if ( (i+1) < argc )
                                {
                                    aErrorFilename = String( argv[ i+1 ], RTL_TEXTENCODING_ASCII_US );
        							bWriteError = sal_True;
                                    i++;
                                }
                                else
                                {
					                fprintf( stderr, "\nERROR: Switch %s requires parameter!\n\n", argv[ i ] );
					                bError = sal_True;
                                }
                            else
       							bWriteError = sal_True;
                        }
						else if ( (*(argv[ i ]+2)) == 'c' )
                            if ( (*(argv[ i ]+3)) == 'f' )
                                if ( (i+1) < argc )
                                {
                                    aCorrectFilename = String( argv[ i+1 ], RTL_TEXTENCODING_ASCII_US );
        							bWriteCorrect = sal_True;
                                    i++;
                                }
                                else
                                {
					                fprintf( stderr, "\nERROR: Switch %s requires parameter!\n\n", argv[ i ] );
					                bError = sal_True;
                                }
                            else
       							bWriteCorrect = sal_True;
						else if ( (*(argv[ i ]+2)) == 'f' )
                            if ( (*(argv[ i ]+3)) == 'f' )
                                if ( (i+1) < argc )
                                {
                                    aFixedFilename = String( argv[ i+1 ], RTL_TEXTENCODING_ASCII_US );
        							bWriteFixed = sal_True;
                                    bFixTags = sal_True;
                                    i++;
                                }
                                else
                                {
					                fprintf( stderr, "\nERROR: Switch %s requires parameter!\n\n", argv[ i ] );
					                bError = sal_True;
                                }
                            else
                            {
       							bWriteFixed = sal_True;
                                bFixTags = sal_True;
                            }
						else
						{
							fprintf( stderr, "\nERROR: Unknown Switch %s!\n\n", argv[ i ] );
							bError = sal_True;
						}
					}
					break;
				case 's':bCheckSourceLang = sal_True;
					break;
				case 't':bCheckTranslationLang = sal_True;
					break;
				case 'l':
                    {
                        if ( (i+1) < argc )
                        {
                            aSourceLang = ByteString( argv[ i+1 ] );
                            if ( aSourceLang.EqualsIgnoreCaseAscii( "none" ) )
                                aSourceLang.Erase();
                            i++;
                        }
                        else
                        {
					        fprintf( stderr, "\nERROR: Switch %s requires parameter!\n\n", argv[ i ] );
					        bError = sal_True;
                        }
                    }
					break;
				case 'r':
                    {
                        if ( (i+1) < argc )
                        {
                            aReferenceFilename = argv[ i+1 ];
                            bReferenceFile = sal_True;
                            i++;
                        }
                        else
                        {
					        fprintf( stderr, "\nERROR: Switch %s requires parameter!\n\n", argv[ i ] );
					        bError = sal_True;
                        }
                    }
					break;
				case 'f':
                    {
                        bFixTags = sal_True;
                    }
					break;
				case 'k':
                    {
                        bAllowKID = sal_True;
                    }
					break;
				case 'e':
                    {
                        bAllowSuspicious = sal_True;
                    }
					break;
				default:
					fprintf( stderr, "\nERROR: Unknown Switch %s!\n\n", argv[ i ] );
					bError = sal_True;
			}
		}
		else
		{
			if  ( !aFilename.Len())
				aFilename = ByteString( argv[ i ] );
			else
			{
				fprintf( stderr, "\nERROR: Only one filename may be specified!\n\n");
				bError = sal_True;
			}
		}
	}


	if ( !aFilename.Len() || bError )
	{
		Help();
		exit ( 0 );
	}

    if ( aSourceLang.Len() && !LanguageOK( aSourceLang ) )
    {
	    fprintf( stderr, "\nERROR: The Language '%s' is invalid!\n\n", aSourceLang.GetBuffer() );
		Help();
		exit ( 1 );
    }

	if ( bCheckSourceLang && bCheckTranslationLang )
    {
	    fprintf( stderr, "\nERROR: The Options -s and -t are mutually exclusive.\nUse only one of them.\n\n" );
		Help();
		exit ( 1 );
    }



	DirEntry aSource = DirEntry( String( aFilename, RTL_TEXTENCODING_ASCII_US ));
	if ( !aSource.Exists()) {
		fprintf( stderr, "\nERROR: GSI-File %s not found!\n\n", aFilename.GetBuffer() );
		exit ( 2 );
	}

	SvFileStream aGSI( String( aFilename, RTL_TEXTENCODING_ASCII_US ), STREAM_STD_READ );
	if ( !aGSI.IsOpen()) {
		fprintf( stderr, "\nERROR: Could not open GSI-File %s!\n\n", aFilename.GetBuffer() );
		exit ( 3 );
	}

    SvFileStream aReferenceGSI;
	if ( bReferenceFile )
    {
        DirEntry aReferenceSource = DirEntry( String( aReferenceFilename, RTL_TEXTENCODING_ASCII_US ));
	    if ( !aReferenceSource.Exists()) {
		    fprintf( stderr, "\nERROR: GSI-File %s not found!\n\n", aFilename.GetBuffer() );
		    exit ( 2 );
	    }

	    aReferenceGSI.Open( String( aReferenceFilename, RTL_TEXTENCODING_ASCII_US ), STREAM_STD_READ );
	    if ( !aReferenceGSI.IsOpen()) {
		    fprintf( stderr, "\nERROR: Could not open Input-File %s!\n\n", aFilename.GetBuffer() );
		    exit ( 3 );
	    }
    }

	LazySvFileStream aOkOut;
	String aBaseName = aSource.GetBase();
	if ( bWriteCorrect )
	{
    	if ( !aCorrectFilename.Len() )
        {
		    String sTmpBase( aBaseName );
		    sTmpBase += String( "_ok", RTL_TEXTENCODING_ASCII_US );
		    aSource.SetBase( sTmpBase );
		    aCorrectFilename = aSource.GetFull();
        }
		aOkOut.SetOpenParams( aCorrectFilename , STREAM_STD_WRITE | STREAM_TRUNC );
	}

	LazySvFileStream aErrOut;
	if ( bWriteError )
	{
    	if ( !aErrorFilename.Len() )
        {
		    String sTmpBase( aBaseName );
		    sTmpBase += String( "_err", RTL_TEXTENCODING_ASCII_US );
		    aSource.SetBase( sTmpBase );
		    aErrorFilename = aSource.GetFull();
        }
		aErrOut.SetOpenParams( aErrorFilename , STREAM_STD_WRITE | STREAM_TRUNC );
	}

	LazySvFileStream aFixOut;
	if ( bWriteFixed )
	{
    	if ( !aFixedFilename.Len() )
        {
		    String sTmpBase( aBaseName );
		    sTmpBase += String( "_fix", RTL_TEXTENCODING_ASCII_US );
		    aSource.SetBase( sTmpBase );
		    aFixedFilename = aSource.GetFull();
        }
		aFixOut.SetOpenParams( aFixedFilename , STREAM_STD_WRITE | STREAM_TRUNC );
	}


    ByteString sReferenceLine;
	GSILine* pReferenceLine = NULL;
	ByteString aOldReferenceId("No Valid ID");   // just set to something which can never be an ID
	sal_uLong nReferenceLine = 0;

	ByteString sGSILine;
	GSILine* pGSILine = NULL;
	ByteString aOldId("No Valid ID");   // just set to something which can never be an ID
	GSIBlock *pBlock = NULL;
	sal_uLong nLine = 0;

	while ( !aGSI.IsEof() )
    {
		aGSI.ReadLine( sGSILine );
		nLine++;
        pGSILine = new GSILine( sGSILine, nLine );
        sal_Bool bDelete = sal_True;


		if ( pGSILine->Len() )
        {
            if ( FORMAT_UNKNOWN == pGSILine->GetLineFormat() )
	        {
		        PrintError( "Format of line is unknown. Ignoring!", "Line format", pGSILine->Copy( 0,40 ), bPrintContext, pGSILine->GetLineNumber() );
		        pGSILine->NotOK();
				if ( bWriteError )
                {
					bFileHasError = sal_True;
                    aErrOut.LazyOpen();
                    aErrOut.WriteLine( *pGSILine );
                }
	        }
            else if ( pGSILine->GetLineType().EqualsIgnoreCaseAscii("res-comment") )
            {   // ignore comment lines, but write them to Correct Items File
			    if ( bWriteCorrect )
                {
                    aOkOut.LazyOpen();
               		aOkOut.WriteLine( *pGSILine );
                }
            }
            else
            {
                ByteString aId = pGSILine->GetUniqId();
			    if ( aId != aOldId )
                {
				    if ( pBlock )
				    {
					    bFileHasError |= pBlock->CheckSyntax( nLine, aSourceLang.Len() != 0, bFixTags );

					    if ( bWriteError )
						    pBlock->WriteError( aErrOut, aSourceLang.Len() != 0 );
					    if ( bWriteCorrect )
						    pBlock->WriteCorrect( aOkOut, aSourceLang.Len() != 0 );
					    if ( bWriteFixed )
						    pBlock->WriteFixed( aFixOut, aSourceLang.Len() != 0 );

					    delete pBlock;
				    }
				    pBlock = new GSIBlock( bPrintContext, bCheckSourceLang, bCheckTranslationLang, bReferenceFile, bAllowKID, bAllowSuspicious );

				    aOldId = aId;


                    // find corresponding line in reference file
                    if ( bReferenceFile )
                    {
                        sal_Bool bContinueSearching = sal_True;
                        while ( ( !aReferenceGSI.IsEof() || pReferenceLine ) && bContinueSearching )
                        {
                            if ( !pReferenceLine )
                            {
		                        aReferenceGSI.ReadLine( sReferenceLine );
		                        nReferenceLine++;
                                pReferenceLine = new GSILine( sReferenceLine, nReferenceLine );
                            }
                            if ( pReferenceLine->GetLineFormat() != FORMAT_UNKNOWN )
                            {
                                if ( pReferenceLine->GetUniqId() == aId && pReferenceLine->GetLanguageId().Equals( aSourceLang ) ) 
                                {
                                    pBlock->SetReferenceLine( pReferenceLine );
                                    pReferenceLine = NULL;
                                }
                                else if ( pReferenceLine->GetUniqId() > aId )
                                {
//                                    if ( pGSILine->GetLanguageId() == aSourceLang )
//                    		            PrintError( "No reference line found. Entry is new in source file", "File format", "", bPrintContext, pGSILine->GetLineNumber(), aId );
                                    bContinueSearching = sal_False;
                                }
                                else
                                {
                                    if ( pReferenceLine->GetUniqId() < aId  && pReferenceLine->GetLanguageId().Equals( aSourceLang ) )
                		                PrintError( "No Entry in source file found. Entry has been removed from source file", "File format", "", bPrintContext, pGSILine->GetLineNumber(), pReferenceLine->GetUniqId() );
                                    delete pReferenceLine;
                                    pReferenceLine = NULL;
                                }
                            }
                            else
                            {
                                delete pReferenceLine;
                                pReferenceLine = NULL;
                            }

                        }
                    }

    		    }

			    pBlock->InsertLine( pGSILine, aSourceLang );
                bDelete = sal_False;
            }
		}
        if ( bDelete )
            delete pGSILine;

	}
	if ( pBlock )
	{
		bFileHasError |= pBlock->CheckSyntax( nLine, aSourceLang.Len() != 0, bFixTags );

		if ( bWriteError )
			pBlock->WriteError( aErrOut, aSourceLang.Len() != 0 );
		if ( bWriteCorrect )
			pBlock->WriteCorrect( aOkOut, aSourceLang.Len() != 0 );
		if ( bWriteFixed )
			pBlock->WriteFixed( aFixOut, aSourceLang.Len() != 0 );

		delete pBlock;
	}
	aGSI.Close();

	if ( bWriteError )
		aErrOut.Close();
	if ( bWriteCorrect )
		aOkOut.Close();
	if ( bWriteFixed )
		aFixOut.Close();

    if ( bFileHasError )
        return 55;
    else
	    return 0;
}
