/* -*- 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 <sal/log.hxx>
#include <rtl/ustrbuf.hxx>

#include "emfppen.hxx"
#include "emfpcustomlinecap.hxx"

using namespace ::com::sun::star;
using namespace ::basegfx;

namespace emfplushelper
{

    EMFPPen::EMFPPen()
        : penDataFlags(0)
        , penUnit(0)
        , penWidth(0.0)
        , startCap(0)
        , endCap(0)
        , maLineJoin(basegfx::B2DLineJoin::Miter)
        , fMiterMinimumAngle(basegfx::deg2rad(5.0))
        , dashStyle(0)
        , dashCap(0)
        , dashOffset(0.0)
        , alignment(0)
        , customStartCapLen(0)
        , customEndCapLen(0)
    {
    }

    EMFPPen::~EMFPPen()
    {
    }

    static OUString PenDataFlagsToString(sal_uInt32 flags)
    {
        rtl::OUStringBuffer sFlags;

        if (flags & EmfPlusPenDataTransform)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataTransform");

        if (flags & EmfPlusPenDataStartCap)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataStartCap");

        if (flags & EmfPlusPenDataEndCap)
             sFlags.append("\nEMF+\t\t\tEmfPlusPenDataEndCap");

        if (flags & EmfPlusPenDataJoin)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataJoin");

        if (flags & EmfPlusPenDataMiterLimit)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataMiterLimit");

        if (flags & EmfPlusPenDataLineStyle)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataLineStyle");

        if (flags & EmfPlusPenDataDashedLineCap)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLineCap");

        if (flags & EmfPlusPenDataDashedLineOffset)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLineOffset");

        if (flags & EmfPlusPenDataDashedLine)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLine");

        if (flags & EmfPlusPenDataAlignment)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataAlignment");

        if (flags & EmfPlusPenDataCompoundLine)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCompoundLine");

        if (flags & EmfPlusPenDataCustomStartCap)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCustomStartCap");

        if (flags & EmfPlusPenDataCustomEndCap)
            sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCustomEndCap");

        return sFlags.makeStringAndClear();
    }

    static OUString LineCapTypeToString(sal_uInt32 linecap)
    {
        switch (linecap)
        {
            case LineCapTypeFlat: return u"LineCapTypeFlat"_ustr;
            case LineCapTypeSquare: return u"LineCapTypeSquare"_ustr;
            case LineCapTypeRound: return u"LineCapTypeRound"_ustr;
            case LineCapTypeTriangle: return u"LineCapTypeTriangle"_ustr;
            case LineCapTypeNoAnchor: return u"LineCapTypeNoAnchor"_ustr;
            case LineCapTypeSquareAnchor: return u"LineCapTypeSquareAnchor"_ustr;
            case LineCapTypeRoundAnchor: return u"LineCapTypeRoundAchor"_ustr;
            case LineCapTypeDiamondAnchor: return u"LineCapTypeDiamondAnchor"_ustr;
            case LineCapTypeArrowAnchor: return u"LineCapTypeArrowAnchor"_ustr;
            case LineCapTypeAnchorMask: return u"LineCapTypeAnchorMask"_ustr;
            case LineCapTypeCustom: return u"LineCapTypeCustom"_ustr;
        }
        return u""_ustr;
    }

    static OUString DashedLineCapTypeToString(sal_uInt32 dashedlinecaptype)
    {
        switch (dashedlinecaptype)
        {
            case DashedLineCapTypeFlat: return u"DashedLineCapTypeFlat"_ustr;
            case DashedLineCapTypeRound: return u"DashedLineCapTypeRound"_ustr;
            case DashedLineCapTypeTriangle: return u"DashedLineCapTypeTriangle"_ustr;
        }
        return u""_ustr;
    }

    static OUString PenAlignmentToString(sal_uInt32 alignment)
    {
        switch (alignment)
        {
            case PenAlignmentCenter: return u"PenAlignmentCenter"_ustr;
            case PenAlignmentInset: return u"PenAlignmentInset"_ustr;
            case PenAlignmentLeft: return u"PenAlignmentLeft"_ustr;
            case PenAlignmentOutset: return u"PenAlignmentOutset"_ustr;
            case PenAlignmentRight: return u"PenAlignmentRight"_ustr;
        }
        return u""_ustr;
    }

    drawinglayer::attribute::StrokeAttribute
    EMFPPen::GetStrokeAttribute(const double aTransformation) const
    {
        if (penDataFlags & EmfPlusPenDataLineStyle // pen has a predefined line style
            && dashStyle != EmfPlusLineStyleCustom)
        {
            const double pw = aTransformation * penWidth;
            switch (dashStyle)
            {
                case EmfPlusLineStyleDash:
                    // [-loplugin:redundantfcast] false positive:
                    return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw });
                case EmfPlusLineStyleDot:
                    // [-loplugin:redundantfcast] false positive:
                    return drawinglayer::attribute::StrokeAttribute({ pw, pw });
                case EmfPlusLineStyleDashDot:
                    // [-loplugin:redundantfcast] false positive:
                    return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw });
                case EmfPlusLineStyleDashDotDot:
                    // [-loplugin:redundantfcast] false positive:
                    return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw, pw, pw });
            }
        }
        else if (penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line
        {
            const double pw = aTransformation * penWidth;
            // StrokeAttribute needs a double vector while the pen provides a float vector
            std::vector<double> aPattern(dashPattern.size());
            for (size_t i = 0; i < aPattern.size(); i++)
            {
                // convert from float to double and multiply with the adjusted pen width
                aPattern[i] = pw * dashPattern[i];
            }
            return drawinglayer::attribute::StrokeAttribute(std::move(aPattern));
        }
        //  EmfPlusLineStyleSolid: - do nothing special, use default stroke attribute
        return drawinglayer::attribute::StrokeAttribute();
    }

    void EMFPPen::Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 dataSize, bool bUseWholeStream)
    {
        sal_Int32 lineJoin = EmfPlusLineJoinTypeMiter;
        sal_uInt32 graphicsVersion(0), penType(0);
        s.ReadUInt32(graphicsVersion).ReadUInt32(penType).ReadUInt32(penDataFlags).ReadUInt32(penUnit).ReadFloat(penWidth);
        SAL_INFO("drawinglayer.emf", "EMF+\t\tGraphics version: 0x" << std::hex << graphicsVersion);
        SAL_INFO("drawinglayer.emf", "EMF+\t\tType: " << penType);
        SAL_INFO("drawinglayer.emf", "EMF+\t\tPen data flags: 0x" << penDataFlags << PenDataFlagsToString(penDataFlags));
        SAL_INFO("drawinglayer.emf", "EMF+\t\tUnit: " << UnitTypeToString(penUnit));
        SAL_INFO("drawinglayer.emf", "EMF+\t\tWidth: " << std::dec << penWidth);

        // If a zero width is specified, a minimum value must be used, which is determined by the units
        if (penWidth == 0.0)
        { //TODO Check if these values is correct
            penWidth = penUnit == 0 ? 0.18f
                : 0.05f;  // 0.05f is taken from old EMF+ implementation (case of Unit == Pixel etc.)
        }

        if (penDataFlags & EmfPlusPenDataTransform)
        {
            EmfPlusHelperData::readXForm(s, pen_transformation);
            SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataTransform: " << pen_transformation);
        }

        if (penDataFlags & EmfPlusPenDataStartCap)
        {
            s.ReadInt32(startCap);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tstartCap: " << LineCapTypeToString(startCap) << " (0x" << std::hex << startCap << ")");
        }
        else
        {
            startCap = 0;
        }

        if (penDataFlags & EmfPlusPenDataEndCap)
        {
            s.ReadInt32(endCap);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tendCap: " << LineCapTypeToString(endCap) << " (0x" << std::hex << startCap << ")");
        }
        else
        {
            endCap = 0;
        }

        if (penDataFlags & EmfPlusPenDataJoin)
        {
            s.ReadInt32(lineJoin);
            SAL_INFO("drawinglayer.emf", "EMF+\t\t LineJoin: " << lineJoin);
            switch (lineJoin)
            {
                case EmfPlusLineJoinTypeBevel:
                    maLineJoin = basegfx::B2DLineJoin::Bevel;
                    break;
                case EmfPlusLineJoinTypeRound:
                    maLineJoin = basegfx::B2DLineJoin::Round;
                    break;
                case EmfPlusLineJoinTypeMiter:
                case EmfPlusLineJoinTypeMiterClipped:
                default: // If nothing set, then apply Miter (based on MS Paint)
                    maLineJoin = basegfx::B2DLineJoin::Miter;
                    break;
            }
        }
        else
            maLineJoin = basegfx::B2DLineJoin::Miter;

        if (penDataFlags & EmfPlusPenDataMiterLimit)
        {
            float miterLimit(0);
            s.ReadFloat(miterLimit);

            // EMF+ JoinTypeMiterClipped is working as our B2DLineJoin::Miter
            // For EMF+ LineJoinTypeMiter we are simulating it by changing angle
            if (lineJoin == EmfPlusLineJoinTypeMiter)
                miterLimit = 3.0 * miterLimit;
            // asin angle must be in range [-1, 1]
            if (abs(miterLimit) > 1.0)
                fMiterMinimumAngle = 2.0 * asin(1.0 / miterLimit);
            else
                // enable miter limit for all angles
                fMiterMinimumAngle = basegfx::deg2rad(180.0);
            SAL_INFO("drawinglayer.emf",
                     "EMF+\t\t MiterLimit: " << std::dec << miterLimit
                                             << ", Miter minimum angle (rad): " << fMiterMinimumAngle);
        }
        else
            fMiterMinimumAngle = basegfx::deg2rad(5.0);


        if (penDataFlags & EmfPlusPenDataLineStyle)
        {
            s.ReadInt32(dashStyle);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tdashStyle: " << DashedLineCapTypeToString(dashStyle) << " (0x" << std::hex << dashStyle << ")");
        }
        else
        {
            dashStyle = 0;
        }

        if (penDataFlags & EmfPlusPenDataDashedLineCap)
        {
            s.ReadInt32(dashCap);
            SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineCap: 0x" << std::hex << dashCap);
        }
        else
        {
            dashCap = 0;
        }

        if (penDataFlags & EmfPlusPenDataDashedLineOffset)
        {
            s.ReadFloat(dashOffset);
            SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineOffset: 0x" << std::hex << dashOffset);
        }
        else
        {
            dashOffset = 0;
        }

        if (penDataFlags & EmfPlusPenDataDashedLine)
        {
            dashStyle = EmfPlusLineStyleCustom;
            sal_uInt32 dashPatternLen(0);

            s.ReadUInt32(dashPatternLen);
            SAL_INFO("drawinglayer.emf", "EMF+\t\t\tdashPatternLen: " << dashPatternLen);

            if (dashPatternLen > s.remainingSize() / sizeof(float))
            {
                SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTruncated dash pattern");
                return;
            }

            dashPattern.resize( dashPatternLen );

            for (sal_uInt32 i = 0; i < dashPatternLen; i++)
            {
                s.ReadFloat(dashPattern[i]);
                SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tdashPattern[" << i << "]: " << dashPattern[i]);
            }
        }

        if (penDataFlags & EmfPlusPenDataAlignment)
        {
            s.ReadInt32(alignment);
            SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataAlignment: " << PenAlignmentToString(alignment) << " (0x" << std::hex << alignment << ")");
        }
        else
        {
            alignment = 0;
        }

        if (penDataFlags & EmfPlusPenDataCompoundLine)
        {
            SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataCompoundLine");
            sal_uInt32 compoundArrayLen(0);
            s.ReadUInt32(compoundArrayLen);

            if (compoundArrayLen > s.remainingSize() / sizeof(float))
            {
                SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTruncated compound array");
                return;
            }

            compoundArray.resize(compoundArrayLen);

            for (sal_uInt32 i = 0; i < compoundArrayLen; i++)
            {
                s.ReadFloat(compoundArray[i]);
                SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tcompoundArray[" << i << "]: " << compoundArray[i]);
            }
        }

        if (penDataFlags & EmfPlusPenDataCustomStartCap)
        {
            s.ReadUInt32(customStartCapLen);
            SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomStartCapLen: " << customStartCapLen);
            sal_uInt64 const pos = s.Tell();

            customStartCap.reset( new EMFPCustomLineCap() );
            customStartCap->Read(s, rR);

            // maybe we don't read everything yet, play it safe ;-)
            s.Seek(pos + customStartCapLen);
        }
        else
        {
            customStartCapLen = 0;
        }

        if (penDataFlags & EmfPlusPenDataCustomEndCap)
        {
            s.ReadUInt32(customEndCapLen);
            SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomEndCapLen: " << customEndCapLen);
            sal_uInt64 const pos = s.Tell();

            customEndCap.reset( new EMFPCustomLineCap() );
            customEndCap->Read(s, rR);

            // maybe we don't read everything yet, play it safe ;-)
            s.Seek(pos + customEndCapLen);
        }
        else
        {
            customEndCapLen = 0;
        }

        EMFPBrush::Read(s, rR, dataSize, bUseWholeStream);
    }
}

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