/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */


#include <comphelper/string.hxx>
#include <string.h>
#include <o3tl/string_view.hxx>
#include <rtl/strbuf.hxx>
#include <vcl/outdev.hxx>
#include <vcl/svapp.hxx>
#include <tools/urlobj.hxx>

#include <svl/urihelper.hxx>
#include <vcl/imap.hxx>
#include <vcl/imapobj.hxx>
#include <vcl/imaprect.hxx>
#include <vcl/imapcirc.hxx>
#include <vcl/imappoly.hxx>

#include <math.h>

#define NOTEOL(c) ((c)!='\0')

void IMapObject::AppendCERNCoords(OStringBuffer& rBuf, const Point& rPoint100)
{
    const Point aPixPt( Application::GetDefaultDevice()->LogicToPixel( rPoint100, MapMode( MapUnit::Map100thMM ) ) );

    rBuf.append('(');
    rBuf.append(static_cast<sal_Int32>(aPixPt.X()));
    rBuf.append(',');
    rBuf.append(static_cast<sal_Int32>(aPixPt.Y()));
    rBuf.append(") ");
}

void IMapObject::AppendNCSACoords(OStringBuffer& rBuf, const Point& rPoint100)
{
    const Point aPixPt( Application::GetDefaultDevice()->LogicToPixel( rPoint100, MapMode( MapUnit::Map100thMM ) ) );

    rBuf.append(static_cast<sal_Int32>(aPixPt.X()));
    rBuf.append(',');
    rBuf.append(static_cast<sal_Int32>(aPixPt.Y()));
    rBuf.append(' ');
}

void IMapObject::AppendCERNURL(OStringBuffer& rBuf) const
{
    rBuf.append(OUStringToOString(URIHelper::simpleNormalizedMakeRelative(u""_ustr, aURL), osl_getThreadTextEncoding()));
}

void IMapObject::AppendNCSAURL(OStringBuffer& rBuf) const
{
    rBuf.append(OUStringToOString(URIHelper::simpleNormalizedMakeRelative(u""_ustr, aURL), osl_getThreadTextEncoding()));
    rBuf.append(' ');
}

void IMapRectangleObject::WriteCERN( SvStream& rOStm ) const
{
    OStringBuffer aStrBuf("rectangle ");

    AppendCERNCoords(aStrBuf, aRect.TopLeft());
    AppendCERNCoords(aStrBuf, aRect.BottomRight());
    AppendCERNURL(aStrBuf);

    rOStm.WriteLine(aStrBuf);
}

void IMapRectangleObject::WriteNCSA( SvStream& rOStm ) const
{
    OStringBuffer aStrBuf("rect ");

    AppendNCSAURL(aStrBuf);
    AppendNCSACoords(aStrBuf, aRect.TopLeft());
    AppendNCSACoords(aStrBuf, aRect.BottomRight());

    rOStm.WriteLine(aStrBuf);
}

void IMapCircleObject::WriteCERN( SvStream& rOStm ) const
{
    OStringBuffer aStrBuf("circle ");

    AppendCERNCoords(aStrBuf, aCenter);
    aStrBuf.append(OString::number(nRadius) + " ");
    AppendCERNURL(aStrBuf);

    rOStm.WriteLine(aStrBuf);
}

void IMapCircleObject::WriteNCSA( SvStream& rOStm ) const
{
    OStringBuffer aStrBuf("circle ");

    AppendNCSAURL(aStrBuf);
    AppendNCSACoords(aStrBuf, aCenter);
    AppendNCSACoords(aStrBuf, aCenter + Point(nRadius, 0));

    rOStm.WriteLine(aStrBuf);
}

void IMapPolygonObject::WriteCERN( SvStream& rOStm  ) const
{
    OStringBuffer aStrBuf("polygon ");
    const sal_uInt16 nCount = aPoly.GetSize();

    for (sal_uInt16 i = 0; i < nCount; ++i)
        AppendCERNCoords(aStrBuf, aPoly[i]);

    AppendCERNURL(aStrBuf);

    rOStm.WriteLine(aStrBuf);
}

void IMapPolygonObject::WriteNCSA( SvStream& rOStm  ) const
{
    OStringBuffer aStrBuf("poly ");
    const sal_uInt16 nCount = std::min( aPoly.GetSize(), sal_uInt16(100) );

    AppendNCSAURL(aStrBuf);

    for (sal_uInt16 i = 0; i < nCount; ++i)
        AppendNCSACoords(aStrBuf, aPoly[i]);

    rOStm.WriteLine(aStrBuf);
}

void ImageMap::Write( SvStream& rOStm, IMapFormat nFormat ) const
{
    switch( nFormat )
    {
        case IMapFormat::Binary : Write( rOStm ); break;
        case IMapFormat::CERN : ImpWriteCERN( rOStm ); break;
        case IMapFormat::NCSA : ImpWriteNCSA( rOStm ); break;

        default:
        break;
    }
}

void ImageMap::ImpWriteCERN( SvStream& rOStm ) const
{
    size_t      nCount = maList.size();

    for ( size_t i = 0; i < nCount; i++ )
    {
        IMapObject* pObj = maList[ i ].get();

        switch( pObj->GetType() )
        {
            case IMapObjectType::Rectangle:
                static_cast<IMapRectangleObject*>( pObj )->WriteCERN( rOStm );
            break;

            case IMapObjectType::Circle:
                static_cast<IMapCircleObject*>( pObj )->WriteCERN( rOStm );
            break;

            case IMapObjectType::Polygon:
                static_cast<IMapPolygonObject*>( pObj )->WriteCERN( rOStm );
            break;

            default:
            break;
        }
    }
}

void ImageMap::ImpWriteNCSA( SvStream& rOStm  ) const
{
    size_t      nCount = maList.size();

    for ( size_t i = 0; i < nCount; i++ )
    {
        IMapObject* pObj = maList[ i ].get();

        switch( pObj->GetType() )
        {
            case IMapObjectType::Rectangle:
                static_cast<IMapRectangleObject*>( pObj )->WriteNCSA( rOStm );
            break;

            case IMapObjectType::Circle:
                static_cast<IMapCircleObject*>( pObj )->WriteNCSA( rOStm );
            break;

            case IMapObjectType::Polygon:
                static_cast<IMapPolygonObject*>( pObj )->WriteNCSA( rOStm );
            break;

            default:
            break;
        }
    }
}

sal_uLong ImageMap::Read( SvStream& rIStm, IMapFormat nFormat  )
{
    sal_uLong nRet = IMAP_ERR_FORMAT;

    if ( nFormat == IMapFormat::Detect )
        nFormat = ImpDetectFormat( rIStm );

    switch ( nFormat )
    {
        case IMapFormat::Binary : Read( rIStm ); break;
        case IMapFormat::CERN   : ImpReadCERN( rIStm ); break;
        case IMapFormat::NCSA   : ImpReadNCSA( rIStm ); break;

        default:
        break;
    }

    if ( !rIStm.GetError() )
        nRet = IMAP_ERR_OK;

    return nRet;
}

void ImageMap::ImpReadCERN( SvStream& rIStm )
{
    // delete old content
    ClearImageMap();

    OStringBuffer aStr;
    while ( rIStm.ReadLine( aStr ) )
        ImpReadCERNLine( aStr );
}

void ImageMap::ImpReadCERNLine( std::string_view rLine  )
{
    OString aStr( comphelper::string::stripStart(rLine, ' ') );
    aStr = comphelper::string::stripStart(aStr, '\t');
    aStr = aStr.replaceAll(";"_ostr, ""_ostr);
    aStr = aStr.toAsciiLowerCase();

    const char* pStr = aStr.getStr();
    char        cChar = *pStr++;

    // find instruction
    OStringBuffer aBuf;
    while ((cChar >= 'a') && (cChar <= 'z'))
    {
        aBuf.append(cChar);
        cChar = *pStr++;
    }
    OString aToken = aBuf.makeStringAndClear();

    if ( !(NOTEOL( cChar )) )
        return;

    if ( ( aToken == "rectangle" ) || ( aToken == "rect" ) )
    {
        const Point     aTopLeft( ImpReadCERNCoords( &pStr ) );
        const Point     aBottomRight( ImpReadCERNCoords( &pStr ) );
        const OUString  aURL( ImpReadCERNURL( &pStr ) );
        const tools::Rectangle aRect( aTopLeft, aBottomRight );

        maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) );
    }
    else if ( ( aToken == "circle" ) || ( aToken == "circ" ) )
    {
        const Point     aCenter( ImpReadCERNCoords( &pStr ) );
        const tools::Long      nRadius = ImpReadCERNRadius( &pStr );
        const OUString  aURL( ImpReadCERNURL( &pStr ) );

        maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) );
    }
    else if ( ( aToken == "polygon" ) || ( aToken == "poly" ) )
    {
        const sal_Int32 nTokenCount = comphelper::string::getTokenCount(aStr, '(');
        tools::Polygon aPoly;
        if (nTokenCount > 0)
        {
            const sal_uInt16 nCount = nTokenCount - 1;
            aPoly.SetSize(nCount);

            for (sal_uInt16 i = 0; i < nCount; ++i)
                aPoly[ i ] = ImpReadCERNCoords( &pStr );
        }

        const OUString aURL = ImpReadCERNURL( &pStr );

        maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) );
    }
}

Point ImageMap::ImpReadCERNCoords( const char** ppStr )
{
    OUStringBuffer  aStrX;
    OUStringBuffer  aStrY;
    Point           aPt;
    char            cChar = *(*ppStr)++;

    while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
        cChar = *(*ppStr)++;

    if ( NOTEOL( cChar ) )
    {
        while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
        {
            aStrX.append( cChar );
            cChar = *(*ppStr)++;
        }

        if ( NOTEOL( cChar ) )
        {
            while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
                cChar = *(*ppStr)++;

            while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
            {
                aStrY.append( cChar );
                cChar = *(*ppStr)++;
            }

            if ( NOTEOL( cChar ) )
                while( NOTEOL( cChar ) && ( cChar != ')' ) )
                    cChar = *(*ppStr)++;

            aPt = Point( o3tl::toInt32(aStrX), o3tl::toInt32(aStrY) );
        }
    }

    return aPt;
}

tools::Long ImageMap::ImpReadCERNRadius( const char** ppStr )
{
    OUStringBuffer  aStr;
    char            cChar = *(*ppStr)++;

    while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
        cChar = *(*ppStr)++;

    if ( NOTEOL( cChar ) )
    {
        while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
        {
            aStr.append( cChar );
            cChar = *(*ppStr)++;
        }
    }

    return o3tl::toInt32(aStr);
}

OUString ImageMap::ImpReadCERNURL( const char** ppStr )
{
    OUString aStr(OUString::createFromAscii(*ppStr));

    aStr = comphelper::string::strip(aStr, ' ');
    aStr = comphelper::string::strip(aStr, '\t');

    return INetURLObject::GetAbsURL( u"", aStr );
}

void ImageMap::ImpReadNCSA( SvStream& rIStm )
{
    // delete old content
    ClearImageMap();

    OStringBuffer aStr;
    while ( rIStm.ReadLine( aStr ) )
        ImpReadNCSALine( aStr );
}

void ImageMap::ImpReadNCSALine( std::string_view rLine )
{
    OString aStr( comphelper::string::stripStart(rLine, ' ') );
    aStr = comphelper::string::stripStart(aStr, '\t');
    aStr = aStr.replaceAll(";"_ostr, ""_ostr);
    aStr = aStr.toAsciiLowerCase();

    const char* pStr = aStr.getStr();
    char        cChar = *pStr++;

    // find instruction
    OStringBuffer aBuf;
    while ((cChar >= 'a') && (cChar <= 'z'))
    {
        aBuf.append(cChar);
        cChar = *pStr++;
    }
    OString aToken = aBuf.makeStringAndClear();

    if ( !(NOTEOL( cChar )) )
        return;

    if ( aToken == "rect" )
    {
        const OUString  aURL( ImpReadNCSAURL( &pStr ) );
        const Point     aTopLeft( ImpReadNCSACoords( &pStr ) );
        const Point     aBottomRight( ImpReadNCSACoords( &pStr ) );
        const tools::Rectangle aRect( aTopLeft, aBottomRight );

        maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) );
    }
    else if ( aToken == "circle" )
    {
        const OUString  aURL( ImpReadNCSAURL( &pStr ) );
        const Point     aCenter( ImpReadNCSACoords( &pStr ) );
        const Point     aDX( aCenter - ImpReadNCSACoords( &pStr ) );
        tools::Long            nRadius = static_cast<tools::Long>(std::hypot( aDX.X(), aDX.Y()));

        maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) );
    }
    else if ( aToken == "poly" )
    {
        const sal_Int32 nTokenCount = comphelper::string::getTokenCount(aStr, ',');
        const OUString aURL( ImpReadNCSAURL( &pStr ) );
        tools::Polygon aPoly;
        if (nTokenCount > 0)
        {
            const sal_uInt16 nCount = nTokenCount - 1;
            aPoly.SetSize(nCount);

            for (sal_uInt16 i = 0; i < nCount; ++i)
                aPoly[ i ] = ImpReadNCSACoords( &pStr );
        }

        maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) );
    }
}

OUString ImageMap::ImpReadNCSAURL( const char** ppStr )
{
    OUStringBuffer  aStr;
    char            cChar = *(*ppStr)++;

    while( NOTEOL( cChar ) && ( ( cChar == ' ' ) || ( cChar == '\t' ) ) )
        cChar = *(*ppStr)++;

    if ( NOTEOL( cChar ) )
    {
        while( NOTEOL( cChar ) && ( cChar != ' ' ) && ( cChar != '\t' ) )
        {
            aStr.append( cChar );
            cChar = *(*ppStr)++;
        }
    }

    return INetURLObject::GetAbsURL( u"", aStr.makeStringAndClear() );
}

Point ImageMap::ImpReadNCSACoords( const char** ppStr )
{
    OUStringBuffer  aStrX;
    OUStringBuffer  aStrY;
    Point           aPt;
    char            cChar = *(*ppStr)++;

    while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
        cChar = *(*ppStr)++;

    if ( NOTEOL( cChar ) )
    {
        while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
        {
            aStrX.append( cChar );
            cChar = *(*ppStr)++;
        }

        if ( NOTEOL( cChar ) )
        {
            while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
                cChar = *(*ppStr)++;

            while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
            {
                aStrY.append( cChar );
                cChar = *(*ppStr)++;
            }

            aPt = Point( o3tl::toInt32(aStrX), o3tl::toInt32(aStrY) );
        }
    }

    return aPt;
}

IMapFormat ImageMap::ImpDetectFormat( SvStream& rIStm )
{
    sal_uInt64  nPos = rIStm.Tell();
    IMapFormat  nRet = IMapFormat::Binary;
    char    cMagic[6];

    rIStm.ReadBytes(cMagic, sizeof(cMagic));

    // if we do not have an internal formats
    // we check the format
    if ( memcmp( cMagic, IMAPMAGIC, sizeof( cMagic ) ) )
    {
        tools::Long        nCount = 128;

        rIStm.Seek( nPos );
        OString aStr;
        while ( rIStm.ReadLine( aStr ) && nCount-- )
        {
            aStr = aStr.toAsciiLowerCase();

            if ( (aStr.indexOf("rect") != -1) ||
                 (aStr.indexOf("circ") != -1) ||
                 (aStr.indexOf("poly") != -1) )
            {
                if ( ( aStr.indexOf('(') != -1 ) &&
                     ( aStr.indexOf(')') != -1 ) )
                {
                    nRet = IMapFormat::CERN;
                }
                else
                    nRet = IMapFormat::NCSA;

                break;
            }
        }
    }

    rIStm.Seek( nPos );

    return nRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
