/* -*- 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 "dlgunit.hxx"
#include <utility>
#include <vcl/fieldvalues.hxx>
#include <vcl/graph.hxx>
#include <vcl/graphicfilter.hxx>
#include <vcl/virdev.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/weld.hxx>
#include <svx/strings.hrc>
#include <svx/svdograf.hxx>
#include <svx/sdgcpitm.hxx>
#include <svx/dialmgr.hxx>
#include <svx/graphichelper.hxx>
#include <svx/compressgraphicdialog.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/module.hxx>
#include <comphelper/fileformat.h>
#include <comphelper/propertyvalue.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <tools/stream.hxx>
#include <unotools/localedatawrapper.hxx>

// tdf#146929 - remember user settings within the current session
// memp is filled in dtor and restored after initialization
namespace
{
    struct memParam {
        bool ReduceResolutionCB = true;
        int  MFNewWidth = 1;
        int  MFNewHeight = 1;
        int  InterpolationCombo = 3;
    };
    memParam memp;
}

using namespace com::sun::star::uno;
using namespace com::sun::star::beans;

CompressGraphicsDialog::CompressGraphicsDialog( weld::Window* pParent, SdrGrafObj* pGraphicObj, SfxBindings& rBindings ) :
    GenericDialogController( pParent, u"svx/ui/compressgraphicdialog.ui"_ustr, u"CompressGraphicDialog"_ustr ),
    m_xGraphicObj     ( pGraphicObj ),
    m_aGraphic        ( pGraphicObj->GetGraphicObject().GetGraphic() ),
    m_aViewSize100mm  ( pGraphicObj->GetLogicRect().GetSize() ),
    m_rBindings       ( rBindings ),
    m_dResolution     ( 300 )
{
    const SdrGrafCropItem& rCrop = m_xGraphicObj->GetMergedItem(SDRATTR_GRAFCROP);
    m_aCropRectangle = tools::Rectangle(rCrop.GetLeft(), rCrop.GetTop(), rCrop.GetRight(), rCrop.GetBottom());

    Initialize();
    recallParameter();
}

CompressGraphicsDialog::CompressGraphicsDialog( weld::Window* pParent, Graphic aGraphic, Size rViewSize100mm, tools::Rectangle const & rCropRectangle, SfxBindings& rBindings ) :
    GenericDialogController( pParent, u"svx/ui/compressgraphicdialog.ui"_ustr, u"CompressGraphicDialog"_ustr ),
    m_xGraphicObj     ( nullptr ),
    m_aGraphic        (std::move( aGraphic )),
    m_aViewSize100mm  ( rViewSize100mm ),
    m_aCropRectangle  ( rCropRectangle ),
    m_rBindings       ( rBindings ),
    m_dResolution     ( 300 )
{
    Initialize();
    recallParameter();
}

CompressGraphicsDialog::~CompressGraphicsDialog()
{
}

void CompressGraphicsDialog::recallParameter()
{
    m_xReduceResolutionCB->set_active( memp.ReduceResolutionCB );
    if (memp.ReduceResolutionCB && (memp.MFNewWidth > 1))
        m_xMFNewWidth->set_value( memp.MFNewWidth );
    if (memp.ReduceResolutionCB && (memp.MFNewHeight > 1))
        m_xMFNewHeight->set_value( memp.MFNewHeight );

    m_xInterpolationCombo->set_active( memp.InterpolationCombo );

    UpdateSensitivity(m_xReduceResolutionCB->get_active());
}

void CompressGraphicsDialog::Initialize()
{
    m_xLabelGraphicType = m_xBuilder->weld_label(u"label-graphic-type"_ustr);
    m_xFixedText2 = m_xBuilder->weld_label(u"label-original-size"_ustr);
    m_xFixedText3 = m_xBuilder->weld_label(u"label-view-size"_ustr);
    m_xFixedText5 = m_xBuilder->weld_label(u"label-image-capacity"_ustr);
    m_xFixedText6 = m_xBuilder->weld_label(u"label-new-capacity"_ustr);
    m_xJpegCompRB = m_xBuilder->weld_radio_button(u"radio-jpeg"_ustr);
    m_xCompressionMF = m_xBuilder->weld_spin_button(u"spin-compression"_ustr);
    m_xCompressionSlider = m_xBuilder->weld_scale(u"scale-compression"_ustr);
    m_xLosslessRB = m_xBuilder->weld_radio_button(u"radio-lossless"_ustr);
    m_xQualityMF = m_xBuilder->weld_spin_button(u"spin-quality"_ustr);
    m_xQualitySlider = m_xBuilder->weld_scale(u"scale-quality"_ustr);
    m_xReduceResolutionCB = m_xBuilder->weld_check_button(u"checkbox-change-resolution"_ustr);
    m_xMFNewWidth = m_xBuilder->weld_spin_button(u"spin-new-width"_ustr);
    m_xMFNewHeight = m_xBuilder->weld_spin_button(u"spin-new-height"_ustr);
    m_xResolutionLB = m_xBuilder->weld_combo_box(u"combo-resolution"_ustr);
    m_xBtnCalculate = m_xBuilder->weld_button(u"calculate"_ustr);
    m_xInterpolationCombo = m_xBuilder->weld_combo_box(u"interpolation-method-combo"_ustr);
    m_xBtnOkay = m_xBuilder->weld_button(u"ok"_ustr);

    m_xInterpolationCombo->set_active_text(u"Lanczos"_ustr);

    m_xInterpolationCombo->connect_changed(LINK(this, CompressGraphicsDialog, NewInterpolationModifiedHdl));

    m_xMFNewWidth->connect_value_changed( LINK( this, CompressGraphicsDialog, NewWidthModifiedHdl ));
    m_xMFNewHeight->connect_value_changed( LINK( this, CompressGraphicsDialog, NewHeightModifiedHdl ));

    m_xResolutionLB->connect_changed( LINK( this, CompressGraphicsDialog, ResolutionModifiedHdl ));
    m_xBtnCalculate->connect_clicked(  LINK( this, CompressGraphicsDialog, CalculateClickHdl ) );

    m_xLosslessRB->connect_toggled( LINK( this, CompressGraphicsDialog, ToggleCompressionRB ) );
    m_xJpegCompRB->connect_toggled( LINK( this, CompressGraphicsDialog, ToggleCompressionRB ) );

    m_xReduceResolutionCB->connect_toggled( LINK( this, CompressGraphicsDialog, ToggleReduceResolutionRB ) );

    m_xQualitySlider->connect_value_changed( LINK( this, CompressGraphicsDialog, SlideHdl ));
    m_xCompressionSlider->connect_value_changed( LINK( this, CompressGraphicsDialog, SlideHdl ));
    m_xQualityMF->connect_value_changed( LINK( this, CompressGraphicsDialog, NewQualityModifiedHdl ));
    m_xCompressionMF->connect_value_changed( LINK( this, CompressGraphicsDialog, NewCompressionModifiedHdl ));

    const auto& pGfxLink = m_aGraphic.GetSharedGfxLink();
    bool bDefaultChoice = (pGfxLink) ? (pGfxLink->GetType() == GfxLinkType::NativeJpg) : false;

    m_xJpegCompRB->set_active(bDefaultChoice);
    m_xQualitySlider->set_sensitive(bDefaultChoice);
    m_xQualityMF->set_sensitive(bDefaultChoice);


    m_xReduceResolutionCB->set_active(true);

    m_xBtnOkay->connect_clicked( LINK( this, CompressGraphicsDialog, OkayClickHdl ) );
    UpdateNewWidthMF();
    UpdateNewHeightMF();
    UpdateResolutionLB();
    Update();
}

void CompressGraphicsDialog::Update()
{
    auto pGfxLink = m_aGraphic.GetSharedGfxLink();

    m_xLabelGraphicType->set_label(GraphicHelper::GetImageType(m_aGraphic));

    const FieldUnit eFieldUnit = m_rBindings.GetDispatcher()->GetModule()->GetFieldUnit();
    const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() );
    sal_Unicode cSeparator = rLocaleWrapper.getNumDecimalSep()[0];

    ScopedVclPtrInstance<VirtualDevice> pDummyVDev;
    pDummyVDev->EnableOutput( false );
    pDummyVDev->SetMapMode( m_aGraphic.GetPrefMapMode() );

    Size aPixelSize = m_aGraphic.GetSizePixel();
    Size aOriginalSize100mm(pDummyVDev->PixelToLogic(m_aGraphic.GetSizePixel(), MapMode(MapUnit::Map100thMM)));

    OUString aBitmapSizeString = SvxResId(STR_IMAGE_ORIGINAL_SIZE);
    OUString aWidthString  = GetUnitString( aOriginalSize100mm.Width(),  eFieldUnit, cSeparator );
    OUString aHeightString = GetUnitString( aOriginalSize100mm.Height(), eFieldUnit, cSeparator );
    aBitmapSizeString = aBitmapSizeString.replaceAll("$(WIDTH)",  aWidthString);
    aBitmapSizeString = aBitmapSizeString.replaceAll("$(HEIGHT)", aHeightString);
    aBitmapSizeString = aBitmapSizeString.replaceAll("$(WIDTH_IN_PX)",  OUString::number(aPixelSize.Width()));
    aBitmapSizeString = aBitmapSizeString.replaceAll("$(HEIGHT_IN_PX)", OUString::number(aPixelSize.Height()));
    m_xFixedText2->set_label(aBitmapSizeString);

    int aValX = static_cast<int>(aPixelSize.Width() / GetViewWidthInch());

    OUString aViewSizeString = SvxResId(STR_IMAGE_VIEW_SIZE);

    aWidthString  = GetUnitString( m_aViewSize100mm.Width(),  eFieldUnit, cSeparator );
    aHeightString = GetUnitString( m_aViewSize100mm.Height(), eFieldUnit, cSeparator );
    aViewSizeString = aViewSizeString.replaceAll("$(WIDTH)",  aWidthString);
    aViewSizeString = aViewSizeString.replaceAll("$(HEIGHT)", aHeightString);
    aViewSizeString = aViewSizeString.replaceAll("$(DPI)", OUString::number(aValX));
    m_xFixedText3->set_label(aViewSizeString);

    m_aNativeSize = pGfxLink ? pGfxLink->GetDataSize() : 0;

    OUString aNativeSizeString = SvxResId(STR_IMAGE_CAPACITY);
    aNativeSizeString = aNativeSizeString.replaceAll("$(CAPACITY)",  OUString::number( m_aNativeSize  / 1024 ));
    m_xFixedText5->set_label(aNativeSizeString);

    m_xFixedText6->set_label(u"??"_ustr);
}

void CompressGraphicsDialog::UpdateSensitivity(const bool bSensitive)
{
    m_xMFNewWidth->set_sensitive(bSensitive);
    m_xMFNewHeight->set_sensitive(bSensitive);
    m_xResolutionLB->set_sensitive(bSensitive);
    m_xInterpolationCombo->set_sensitive(bSensitive);
}

void CompressGraphicsDialog::UpdateNewWidthMF()
{
    int nPixelX = static_cast<sal_Int32>( GetViewWidthInch() * m_dResolution );
    m_xMFNewWidth->set_value(nPixelX);
}

void CompressGraphicsDialog::UpdateNewHeightMF()
{
    int nPixelY = static_cast<sal_Int32>( GetViewHeightInch() * m_dResolution );
    m_xMFNewHeight->set_value(nPixelY);
}

void CompressGraphicsDialog::UpdateResolutionLB()
{
    m_xResolutionLB->set_entry_text( OUString::number( static_cast<sal_Int32>(m_dResolution) ) );
}

double CompressGraphicsDialog::GetViewWidthInch() const
{
    return static_cast<double>(vcl::ConvertAndScaleValue(m_aViewSize100mm.Width(),  2, MapUnit::Map100thMM, FieldUnit::INCH)) / 100.0;
}

double CompressGraphicsDialog::GetViewHeightInch() const
{
    return static_cast<double>(vcl::ConvertAndScaleValue(m_aViewSize100mm.Height(),  2, MapUnit::Map100thMM, FieldUnit::INCH)) / 100.0;
}

BmpScaleFlag CompressGraphicsDialog::GetSelectedInterpolationType() const
{
    OUString aSelectionText = m_xInterpolationCombo->get_active_text();

    if( aSelectionText == "Lanczos" ) {
        return BmpScaleFlag::Lanczos;
    } else if( aSelectionText == "Bilinear" ) {
        return BmpScaleFlag::BiLinear;
    } else if( aSelectionText == "Bicubic" ) {
        return BmpScaleFlag::BiCubic;
    } else if ( aSelectionText == "None" ) {
        return BmpScaleFlag::Fast;
    }
    return BmpScaleFlag::BestQuality;
}

void CompressGraphicsDialog::Compress(SvStream& aStream)
{
    Bitmap aBitmap = m_aGraphic.GetBitmap();
    if ( m_xReduceResolutionCB->get_active() )
    {
        tools::Long nPixelX = static_cast<tools::Long>( GetViewWidthInch() * m_dResolution );
        tools::Long nPixelY = static_cast<tools::Long>( GetViewHeightInch() * m_dResolution );

        aBitmap.Scale( Size( nPixelX, nPixelY ), GetSelectedInterpolationType() );
    }
    Graphic aScaledGraphic( aBitmap );
    GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();

    Sequence< PropertyValue > aFilterData{
        comphelper::makePropertyValue(u"Interlaced"_ustr, sal_Int32(0)),
        comphelper::makePropertyValue(u"Compression"_ustr, static_cast<sal_Int32>(m_xCompressionMF->get_value())),
        comphelper::makePropertyValue(u"Quality"_ustr, static_cast<sal_Int32>(m_xQualityMF->get_value()))
    };

    OUString aGraphicFormatName = m_xLosslessRB->get_active() ? u"png"_ustr : u"jpg"_ustr;

    sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName( aGraphicFormatName );
    rFilter.ExportGraphic( aScaledGraphic, u"none", aStream, nFilterFormat, &aFilterData );
}

IMPL_LINK_NOARG( CompressGraphicsDialog, OkayClickHdl, weld::Button&, void )
{
    memp.ReduceResolutionCB = m_xReduceResolutionCB->get_active();
    memp.MFNewWidth =         m_xMFNewWidth->get_value();
    memp.MFNewHeight =        m_xMFNewHeight->get_value();
    memp.InterpolationCombo = m_xInterpolationCombo->get_active();
    CompressGraphicsDialog::response(RET_OK);
}

IMPL_LINK_NOARG( CompressGraphicsDialog, NewWidthModifiedHdl, weld::SpinButton&, void )
{
    m_dResolution =  m_xMFNewWidth->get_value() / GetViewWidthInch();

    UpdateNewHeightMF();
    UpdateResolutionLB();
    Update();
}

IMPL_LINK( CompressGraphicsDialog, SlideHdl, weld::Scale&, rScale, void )
{
    if (&rScale == m_xQualitySlider.get())
        m_xQualityMF->set_value(m_xQualitySlider->get_value());
    else
        m_xCompressionMF->set_value(m_xCompressionSlider->get_value());
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, NewInterpolationModifiedHdl, weld::ComboBox&, void )
{
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, NewQualityModifiedHdl, weld::SpinButton&, void )
{
    m_xQualitySlider->set_value(m_xQualityMF->get_value());
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, NewCompressionModifiedHdl, weld::SpinButton&, void )
{
    m_xCompressionSlider->set_value(m_xCompressionMF->get_value());
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, NewHeightModifiedHdl, weld::SpinButton&, void )
{
    m_dResolution =  m_xMFNewHeight->get_value() / GetViewHeightInch();

    UpdateNewWidthMF();
    UpdateResolutionLB();
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, ResolutionModifiedHdl, weld::ComboBox&, void )
{
    m_dResolution = static_cast<double>(m_xResolutionLB->get_active_text().toInt32());

    UpdateNewWidthMF();
    UpdateNewHeightMF();
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, ToggleCompressionRB, weld::Toggleable&, void )
{
    bool choice = m_xLosslessRB->get_active();
    m_xCompressionMF->set_sensitive(choice);
    m_xCompressionSlider->set_sensitive(choice);
    m_xQualityMF->set_sensitive(!choice);
    m_xQualitySlider->set_sensitive(!choice);
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, ToggleReduceResolutionRB, weld::Toggleable&, void )
{
    UpdateSensitivity(m_xReduceResolutionCB->get_active());
    Update();
}

IMPL_LINK_NOARG( CompressGraphicsDialog, CalculateClickHdl, weld::Button&, void )
{
    sal_Int32 aSize = 0;

    if ( m_dResolution > 0.0  )
    {
        SvMemoryStream aMemStream;
        aMemStream.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
        Compress( aMemStream );
        aSize = aMemStream.TellEnd();
    }

    if ( aSize > 0 )
    {
        OUString aSizeAsString = OUString::number(aSize / 1024);

        OUString aReductionSizeAsString;
        if (m_aNativeSize > 0 )
           aReductionSizeAsString = OUString::number( static_cast<sal_Int32>((m_aNativeSize - aSize) * 100.0 / m_aNativeSize) );
        else
           aReductionSizeAsString = "0";

        OUString aNewSizeString = SvxResId(STR_IMAGE_CAPACITY_WITH_REDUCTION);
        aNewSizeString = aNewSizeString.replaceAll("$(CAPACITY)", aSizeAsString);
        aNewSizeString = aNewSizeString.replaceAll("$(REDUCTION)", aReductionSizeAsString);
        m_xFixedText6->set_label(aNewSizeString);
    }
}

tools::Rectangle CompressGraphicsDialog::GetScaledCropRectangle() const
{
    if ( m_xReduceResolutionCB->get_active() )
    {
        tools::Long nPixelX = static_cast<tools::Long>( GetViewWidthInch()  * m_dResolution );
        tools::Long nPixelY = static_cast<tools::Long>( GetViewHeightInch() * m_dResolution );
        Size aSize = m_aGraphic.GetBitmap().GetSizePixel();
        double aScaleX = nPixelX / static_cast<double>(aSize.Width());
        double aScaleY = nPixelY / static_cast<double>(aSize.Height());

        return tools::Rectangle(
            m_aCropRectangle.Left()  * aScaleX,
            m_aCropRectangle.Top()   * aScaleY,
            m_aCropRectangle.Right() * aScaleX,
            m_aCropRectangle.Bottom()* aScaleY);
    }
    else
    {
        return m_aCropRectangle;
    }
}

Graphic CompressGraphicsDialog::GetCompressedGraphic()
{
    if ( m_dResolution > 0.0  )
    {
        SvMemoryStream aMemStream;
        aMemStream.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
        Compress( aMemStream );
        aMemStream.Seek( STREAM_SEEK_TO_BEGIN );
        Graphic aResultGraphic;
        GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
        rFilter.ImportGraphic( aResultGraphic, u"import", aMemStream );

        return aResultGraphic;
    }
    return Graphic();
}

rtl::Reference<SdrGrafObj> CompressGraphicsDialog::GetCompressedSdrGrafObj()
{
    if ( m_dResolution > 0.0  )
    {
        rtl::Reference<SdrGrafObj> pNewObject = SdrObject::Clone(*m_xGraphicObj, m_xGraphicObj->getSdrModelFromSdrObject());

        if ( m_xReduceResolutionCB->get_active() )
        {
            tools::Rectangle aScaledCropedRectangle = GetScaledCropRectangle();
            SdrGrafCropItem aNewCrop(
                aScaledCropedRectangle.Left(),
                aScaledCropedRectangle.Top(),
                aScaledCropedRectangle.Right(),
                aScaledCropedRectangle.Bottom());

            pNewObject->SetMergedItem(aNewCrop);
        }
        pNewObject->SetGraphic( GetCompressedGraphic() );

        return pNewObject;
    }
    return nullptr;
}

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