/************************************************************************
 *
 * Copyright (C) 2009-2022 IRCAD France
 * Copyright (C) 2012-2020 IHU Strasbourg
 *
 * This file is part of Sight.
 *
 * Sight is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sight is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Sight. If not, see <https://www.gnu.org/licenses/>.
 *
 ***********************************************************************/

#include "itkInrImageIO.hpp"

#include <core/Exception.hpp>
#include <core/spyLog.hpp>

#include <boost/lexical_cast.hpp>

#include <itk_zlib.h>
#include <itkByteSwapper.h>
#include <itkIOCommon.h>
#include <itkMacro.h>

#include <algorithm>
#include <cstdio>
#include <map>
#include <utility>

#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) <= (b) ? (a) : (b))

namespace itk
{

InrImageIO::InrImageIO()
{
    this->SetNumberOfDimensions(3);
    m_PixelType = SCALAR;
    //m_swapBytes = false;
}

//------------------------------------------------------------------------------

InrImageIO::~InrImageIO()
{
    // Nothing to do.
}

//------------------------------------------------------------------------------

void InrImageIO::PrintSelf(std::ostream& os, Indent indent) const
{
    Superclass::PrintSelf(os, indent);
    os << indent << "PixelType " << m_PixelType << "\n";
    os << indent << "ComponentType " << m_ComponentType << "\n";
}

//------------------------------------------------------------------------------

bool InrImageIO::CanReadFile(const char* FileNameToRead)
{
    // Do not perform extension checking, magic number in the header is better.
    gzFile inputFile = gzopen(FileNameToRead, "rb");
    if(inputFile == 0)
    {
        return false;
    }

    char firstLine[256];

    gzgets(inputFile, firstLine, 256);

    gzclose(inputFile);

    std::string const magic("#INRIMAGE-4#{\n");
    if(firstLine != magic)
    {
        return false;
    }
    else
    {
        return true;
    }
}

//------------------------------------------------------------------------------

void InrImageIO::ReadImageInformation()
{
    gzFile inputFile = gzopen(this->GetFileName(), "rb");
    if(inputFile == NULL)
    {
        gzclose(inputFile);
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "File " << GetFileName() << " could not be read";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    const int lineBufSize = 256;
    char linebuf[lineBufSize];
    std::map<std::string, std::string> headerValues;
    m_headerSize = 0;
    while(gzgets(inputFile, linebuf, lineBufSize) && !gzeof(inputFile))
    {
        std::string line(linebuf);
        // Count the number of characters we just read : length of the line
        m_headerSize += line.length();
        // Remove eventual trailing '\n'
        std::string::size_type const endOfLine = line.find('\n');
        line = line.substr(0, endOfLine);

        if(line == "##}")
        {
            // End of header : get outta here
            break;
        }

        if(line.empty())
        {
            // Empty line, nothing to do
        }
        else if(line[0] == '#')
        {
            // Comment line, nothing to do
        }
        else
        {
            // Look for key=value
            std::string::const_iterator delimiter = std::find(line.begin(), line.end(), '=');
            if(delimiter == line.end())
            {
                // invalid line : missing '='
                gzclose(inputFile);
                ExceptionObject exception(__FILE__, __LINE__);
                std::stringstream errorMessage;
                errorMessage << "Invalid INR file.\n" << "Offending line : " << line;
                exception.SetDescription(errorMessage.str());
                throw exception;
            }

            // Tokenize [KEY] = [VALUE] and store it in map
            std::string key   = line.substr(0, delimiter - line.begin());
            std::string value = line.substr(delimiter - line.begin() + 1, std::string::npos);
            if(headerValues.find(key) != headerValues.end())
            {
                // duplicate key
                ExceptionObject exception(__FILE__, __LINE__);
                gzclose(inputFile);
                std::stringstream errorMessage;
                errorMessage << "Invalid INR file.\n" << "Duplicate key : " << key;
                exception.SetDescription(errorMessage.str());
                throw exception;
            }
            else
            {
                headerValues[key] = value;
            }
        }
    }

    // We have finished reading the header
    gzclose(inputFile);

    // Process headerValue map
    for(std::map<std::string, std::string>::const_iterator it = headerValues.begin() ; it != headerValues.end() ; ++it)
    {
        if(it->first == "XDIM")
        {
            int xdim = boost::lexical_cast<int>(it->second);
            this->SetDimensions(0, xdim);
        }
        else if(it->first == "YDIM")
        {
            int ydim = boost::lexical_cast<int>(it->second);
            this->SetDimensions(1, ydim);
        }
        else if(it->first == "ZDIM")
        {
            int zdim = boost::lexical_cast<int>(it->second);
            this->SetDimensions(2, zdim);
        }
        else if(it->first == "VDIM")
        {
            int vdim = boost::lexical_cast<int>(it->second);
            if(vdim == 1)
            {
                SetPixelType(SCALAR);
            }
            else
            {
                SetPixelType(VECTOR);
            }

            SetNumberOfComponents(vdim);
        }
        else if(it->first == "VX")
        {
            float vx = boost::lexical_cast<float>(it->second);
            this->SetSpacing(0, vx);
        }
        else if(it->first == "VY")
        {
            float vy = boost::lexical_cast<float>(it->second);
            this->SetSpacing(1, vy);
        }
        else if(it->first == "VZ")
        {
            float vz = boost::lexical_cast<float>(it->second);
            this->SetSpacing(2, vz);
        }
        else if(it->first == "TYPE")
        {
            std::map<std::string, std::string>::const_iterator pixsize_it = headerValues.find("PIXSIZE");
            if(pixsize_it == headerValues.end())
            {
                // Missing pixsize
                ExceptionObject exception(__FILE__, __LINE__);
                std::stringstream errorMessage;
                errorMessage << "Invalid INR file.\n" << "PIXSIZE key not found.";
                exception.SetDescription(errorMessage.str());
                throw exception;
            }

            std::string type    = it->second;
            std::string pixsize = pixsize_it->second;
            if(type == "unsigned fixed")
            {
                if(pixsize == "8 bits")
                {
                    m_ComponentType = UCHAR;
                }
                else if(pixsize == "16 bits")
                {
                    m_ComponentType = USHORT;
                }
                else if(pixsize == "32 bits")
                {
                    if(4 == sizeof(unsigned int))
                    {
                        m_ComponentType = UINT;
                    }
                    else if(4 == sizeof(unsigned long))
                    {
                        m_ComponentType = ULONG;
                    }
                    else
                    {
                        ExceptionObject exception(__FILE__, __LINE__);
                        std::stringstream errorMessage;
                        errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                        exception.SetDescription(errorMessage.str());
                        throw exception;
                    }
                }
                else if(pixsize == "64 bits")
                {
                    if(8 == sizeof(unsigned int))
                    {
                        m_ComponentType = UINT;
                    }
                    else if(8 == sizeof(unsigned long))
                    {
                        m_ComponentType = ULONG;
                    }
                    else
                    {
                        ExceptionObject exception(__FILE__, __LINE__);
                        std::stringstream errorMessage;
                        errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                        exception.SetDescription(errorMessage.str());
                        throw exception;
                    }
                }
                else
                {
                    ExceptionObject exception(__FILE__, __LINE__);
                    std::stringstream errorMessage;
                    errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                    exception.SetDescription(errorMessage.str());
                    throw exception;
                }
            }
            else if(type == "signed fixed")
            {
                if(pixsize == "8 bits")
                {
                    m_ComponentType = CHAR;
                }
                else if(pixsize == "16 bits")
                {
                    m_ComponentType = SHORT;
                }
                else if(pixsize == "32 bits")
                {
                    if(4 == sizeof(int))
                    {
                        m_ComponentType = INT;
                    }
                    else if(4 == sizeof(long))
                    {
                        m_ComponentType = LONG;
                    }
                    else
                    {
                        ExceptionObject exception(__FILE__, __LINE__);
                        std::stringstream errorMessage;
                        errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                        exception.SetDescription(errorMessage.str());
                        throw exception;
                    }
                }
                else if(pixsize == "64 bits")
                {
                    if(8 == sizeof(int))
                    {
                        m_ComponentType = INT;
                    }
                    else if(8 == sizeof(long))
                    {
                        m_ComponentType = LONG;
                    }
                    else
                    {
                        ExceptionObject exception(__FILE__, __LINE__);
                        std::stringstream errorMessage;
                        errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                        exception.SetDescription(errorMessage.str());
                        throw exception;
                    }
                }
                else
                {
                    ExceptionObject exception(__FILE__, __LINE__);
                    std::stringstream errorMessage;
                    errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                    exception.SetDescription(errorMessage.str());
                    throw exception;
                }
            }
            else if(type == "float")
            {
                /*
                   if(pixsize=="8 bits")
                   {
                   }
                   else if(pixsize=="16 bits")
                   {
                   }
                 */
                /*else*/ if(pixsize == "32 bits")
                {
                    m_ComponentType = FLOAT;
                }
                else if(pixsize == "64 bits")
                {
                    m_ComponentType = DOUBLE;
                }
                else
                {
                    ExceptionObject exception(__FILE__, __LINE__);
                    std::stringstream errorMessage;
                    errorMessage << "Invalid INR file.\n" << "Invalid PIXSIZE.";
                    exception.SetDescription(errorMessage.str());
                    throw exception;
                }
            }
            else
            {
                ExceptionObject exception(__FILE__, __LINE__);
                std::stringstream errorMessage;
                errorMessage << "Invalid INR file.\n" << "Invalid TYPE.";
                exception.SetDescription(errorMessage.str());
                throw exception;
            }
        } // TYPE
        else if(it->first == "PIXSIZE")
        {
            // nothing to do, processed with "TYPE"
        }
        else if(it->first == "SCALE")
        {
            // For fixed point arithmetic only. We don't use it.
        }
        else if(it->first == "CPU")
        {
            // Big endian are : sun, sgi
            if(it->second == "sun" || it->second == "sgi")
            {
                // File is big-endian, swap if system is little endian.
                //m_swapBytes = itk::ByteSwapper<char>::SystemIsLE();
                m_ByteOrder = BigEndian;
            }
            // Little endian are : pc, alpha, decm
            else if(it->second == "pc" || it->second == "alpha" || it->second == "decm")
            {
                // File is little-endian, swap if system is big-endian.
                //m_swapBytes = itk::ByteSwapper<char>::SystemIsBE();
                m_ByteOrder = LittleEndian;
            }
            else
            {
                ExceptionObject exception(__FILE__, __LINE__);
                std::stringstream errorMessage;
                errorMessage << "Invalid INR file.\n" << "Unknown CPU value : " << it->second;
                exception.SetDescription(errorMessage.str());
                throw exception;
            }
        }
    }
}

//------------------------------------------------------------------------------

void InrImageIO::Read(void* buffer)
{
    gzFile file = gzopen(GetFileName(), "rb");

    if(file == NULL)
    {
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "INR file " << GetFileName() << " could not be read";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    // Skip the header
    int bytesSkipped = gzseek(file, m_headerSize, SEEK_CUR);
    if(bytesSkipped != m_headerSize)
    {
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "INR file " << GetFileName() << " could not be read (header size : " << bytesSkipped << "/"
        << m_headerSize << ").";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    // And read the buffer

    // Replace this line :
    //int bytesRead = gzread(file, buffer, GetImageSizeInBytes());
    // by
    int bytesRead      = 0;
    int nbStep         = 10;
    long long int size =
        ((GetImageSizeInBytes() > 1024 * 1024 * 10) ? (GetImageSizeInBytes() / nbStep) + 1 : GetImageSizeInBytes());
    int step = 0;
    try
    {
        while(bytesRead < GetImageSizeInBytes() && step < nbStep)
        {
            step++;
            UpdateProgress(((float) bytesRead) / GetImageSizeInBytes());
            bytesRead += gzread(file, ((char*) buffer) + bytesRead, min(size, GetImageSizeInBytes() - bytesRead));
        }
    }
    catch(sight::core::Exception& e) // catch progress bar cancel exception
    {
        gzclose(file);
        throw;
    }
    UpdateProgress(1.0);
    // End replace

    if(bytesRead != GetImageSizeInBytes())
    {
        gzclose(file);
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "INR file " << GetFileName() << " could not be read";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    // Swap bytes if necessary
    if(m_ByteOrder == LittleEndian)
    {
        switch(m_ComponentType)
        {
            case CHAR:
                ByteSwapper<char>::SwapRangeFromSystemToLittleEndian((char*) buffer, GetImageSizeInComponents());
                break;

            case UCHAR:
                ByteSwapper<unsigned char>::SwapRangeFromSystemToLittleEndian(
                    (unsigned char*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case SHORT:
                ByteSwapper<short>::SwapRangeFromSystemToLittleEndian((short*) buffer, GetImageSizeInComponents());
                break;

            case USHORT:
                ByteSwapper<unsigned short>::SwapRangeFromSystemToLittleEndian(
                    (unsigned short*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case INT:
                ByteSwapper<int>::SwapRangeFromSystemToLittleEndian((int*) buffer, GetImageSizeInComponents());
                break;

            case UINT:
                ByteSwapper<unsigned int>::SwapRangeFromSystemToLittleEndian(
                    (unsigned int*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case LONG:
                ByteSwapper<long>::SwapRangeFromSystemToLittleEndian((long*) buffer, GetImageSizeInComponents());
                break;

            case ULONG:
                ByteSwapper<unsigned long>::SwapRangeFromSystemToLittleEndian(
                    (unsigned long*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case FLOAT:
                ByteSwapper<float>::SwapRangeFromSystemToLittleEndian((float*) buffer, GetImageSizeInComponents());
                break;

            case DOUBLE:
                ByteSwapper<double>::SwapRangeFromSystemToLittleEndian((double*) buffer, GetImageSizeInComponents());
                break;

            default:
                gzclose(file);
                ExceptionObject exception(__FILE__, __LINE__);
                exception.SetDescription("Pixel Type Unknown");
                throw exception;
        }
    }
    else if(m_ByteOrder == BigEndian)
    {
        switch(m_ComponentType)
        {
            case CHAR:
                ByteSwapper<char>::SwapRangeFromSystemToBigEndian((char*) buffer, GetImageSizeInComponents());
                break;

            case UCHAR:
                ByteSwapper<unsigned char>::SwapRangeFromSystemToBigEndian(
                    (unsigned char*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case SHORT:
                ByteSwapper<short>::SwapRangeFromSystemToBigEndian((short*) buffer, GetImageSizeInComponents());
                break;

            case USHORT:
                ByteSwapper<unsigned short>::SwapRangeFromSystemToBigEndian(
                    (unsigned short*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case INT:
                ByteSwapper<int>::SwapRangeFromSystemToBigEndian((int*) buffer, GetImageSizeInComponents());
                break;

            case UINT:
                ByteSwapper<unsigned int>::SwapRangeFromSystemToBigEndian(
                    (unsigned int*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case LONG:
                ByteSwapper<long>::SwapRangeFromSystemToBigEndian((long*) buffer, GetImageSizeInComponents());
                break;

            case ULONG:
                ByteSwapper<unsigned long>::SwapRangeFromSystemToBigEndian(
                    (unsigned long*) buffer,
                    GetImageSizeInComponents()
                );
                break;

            case FLOAT:
                ByteSwapper<float>::SwapRangeFromSystemToBigEndian((float*) buffer, GetImageSizeInComponents());
                break;

            case DOUBLE:
                ByteSwapper<double>::SwapRangeFromSystemToBigEndian((double*) buffer, GetImageSizeInComponents());
                break;

            default:
                gzclose(file);
                ExceptionObject exception(__FILE__, __LINE__);
                exception.SetDescription("Pixel Type Unknown");
                throw exception;
        }
    }

    gzclose(file);
}

//------------------------------------------------------------------------------

bool InrImageIO::CanWriteFile(const char* FileNameToWrite)
{
    // Extension must be .inr or .inr.gz
    std::string const filename(FileNameToWrite);

    std::string::size_type index = filename.rfind(".inr");
    if(index == filename.length() - std::string(".inr").length())
    {
        return true;
    }

    index = filename.rfind(".inr.gz");
    if(index == filename.length() - std::string(".inr.gz").length())
    {
        return true;
    }

    return false;
}

//------------------------------------------------------------------------------

void InrImageIO::WriteImageInformation()
{
    std::stringstream headerStream;

    // Magic
    headerStream << "#INRIMAGE-4#{" << "\n";
    // Dimensions : always write a 3D InrImage
    headerStream << "XDIM=" << ((GetDimensions(0) < 1) ? 1 : GetDimensions(0)) << "\n";
    headerStream << "YDIM=" << ((GetDimensions(1) < 1) ? 1 : GetDimensions(1)) << "\n";
    headerStream << "ZDIM=" << ((GetDimensions(2) < 1) ? 1 : GetDimensions(2)) << "\n";
    // Number of components per pixel
    headerStream << "VDIM=" << GetNumberOfComponents() << "\n";
    // Spacing : if it's 0, put it to one instead
    headerStream << "VX=" << ((GetSpacing(0) == 0.0) ? 1.0 : GetSpacing(0)) << "\n";
    headerStream << "VY=" << ((GetSpacing(1) == 0.0) ? 1.0 : GetSpacing(1)) << "\n";
    headerStream << "VZ=" << ((GetSpacing(2) == 0.0) ? 1.0 : GetSpacing(2)) << "\n";
    // Scale
    headerStream << "SCALE=2**0" << "\n";
    // Endianness
    headerStream << "CPU=" << (itk::ByteSwapper<char>::SystemIsLE() ? "pc" : "sgi") << "\n";
    // Point type
    std::string type;
    if(m_ComponentType == UCHAR || m_ComponentType == USHORT || m_ComponentType == UINT || m_ComponentType == ULONG)
    {
        type = "unsigned fixed";
    }
    else if(m_ComponentType == CHAR || m_ComponentType == SHORT || m_ComponentType == INT || m_ComponentType == LONG)
    {
        type = "signed fixed";
    }
    else if(m_ComponentType == FLOAT || m_ComponentType == DOUBLE)
    {
        type = "float";
    }
    else
    {
        // Unknown/incorrect component type
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "File " << GetFileName()
        << " could not be written : unknown pixel type.";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    headerStream << "TYPE=" << type << "\n";
    // PixelSize
    int pixelSize;
    if(m_ComponentType == UCHAR || m_ComponentType == CHAR)
    {
        pixelSize = sizeof(char);
    }
    else if(m_ComponentType == USHORT || m_ComponentType == SHORT)
    {
        pixelSize = sizeof(short);
    }
    else if(m_ComponentType == UINT || m_ComponentType == INT)
    {
        pixelSize = sizeof(int);
    }
    else if(m_ComponentType == ULONG || m_ComponentType == LONG)
    {
        pixelSize = sizeof(long);
    }
    else if(m_ComponentType == FLOAT)
    {
        pixelSize = sizeof(float);
    }
    else if(m_ComponentType == DOUBLE)
    {
        pixelSize = sizeof(double);
    }
    else
    {
        // Unknown/incorrect component type
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "File " << GetFileName()
        << " could not be written : unknown pixel type.";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    pixelSize *= 8;
    headerStream << "PIXSIZE=" << pixelSize << " bits" << "\n";

    int const padding = 256 - headerStream.str().length() - std::string("##}\n").length();
    for(int i = 0 ; i < padding ; ++i)
    {
        headerStream << "\n";
    }

    headerStream << "##}\n";
    if(!headerStream)
    {
        // Something could not be written to headerStream
        ExceptionObject exception(__FILE__, __LINE__);
        std::stringstream errorMessage;
        errorMessage << "File " << GetFileName()
        << " could not be written : error while writing to headerStream.";
        exception.SetDescription(errorMessage.str());
        throw exception;
    }

    std::string const header = headerStream.str();
    std::string const filename(this->GetFileName());
    std::string::size_type const index = filename.rfind(".inr.gz");

    if(index == filename.length() - std::string(".inr.gz").length())
    {
        gzFile outputFile = gzopen(this->GetFileName(), "wb9");
        if(outputFile == 0)
        {
            ExceptionObject exception(__FILE__, __LINE__);
            std::stringstream errorMessage;
            errorMessage << "File " << GetFileName()
            << " could not be written : error writing header.";
            exception.SetDescription(errorMessage.str());
            throw exception;
        }

        std::string const headerString = headerStream.str();
        gzwrite(outputFile, const_cast<char*>(headerString.c_str()), headerString.length());
        gzclose(outputFile);
    }
    else
    {
        FILE* outputFile = fopen(this->GetFileName(), "wb");
        if(outputFile == 0)
        {
            ExceptionObject exception(__FILE__, __LINE__);
            std::stringstream errorMessage;
            errorMessage << "File " << GetFileName()
            << " could not be written : error writing header.";
            exception.SetDescription(errorMessage.str());
            throw exception;
        }

        std::string const headerString = headerStream.str();
        fwrite(headerString.c_str(), sizeof(char), headerString.length(), outputFile);
        fclose(outputFile);
    }
}

//------------------------------------------------------------------------------

void InrImageIO::Write(const void* buffer)
{
    this->WriteImageInformation();
    std::string const filename(GetFileName());
    std::string const suffix(".inr.gz");
    std::string::size_type const index = filename.rfind(suffix);
    if(index == filename.length() - suffix.length())
    {
        gzFile outputFile = gzopen(this->GetFileName(), "ab");
        if(outputFile == 0)
        {
            ExceptionObject exception(__FILE__, __LINE__);
            std::stringstream errorMessage;
            errorMessage << "File " << GetFileName()
            << " could not be written : error writing buffer.";
            exception.SetDescription(errorMessage.str());
            throw exception;
        }

        // Replace this line :
        // gzwrite(outputFile, const_cast<void*>(buffer), GetImageSizeInBytes());
        // by
        int written        = 0;
        long long int size =
            ((GetImageSizeInBytes() > 1024 * 1024 * 10) ? (GetImageSizeInBytes() / 10) + 1 : GetImageSizeInBytes());
        try
        {
            while(written < GetImageSizeInBytes())
            {
                UpdateProgress(((float) written) / GetImageSizeInBytes());
                written +=
                    gzwrite(outputFile, ((char*) buffer) + written, min(size, GetImageSizeInBytes() - written));
            }
        }
        catch(sight::core::Exception& e) // catch progress bar cancel exception
        {
            gzclose(outputFile);
            throw;
        }
        UpdateProgress(1.0);
        // End replace

        gzclose(outputFile);
    }
    else
    {
        FILE* outputFile = fopen(this->GetFileName(), "ab");
        if(outputFile == 0)
        {
            ExceptionObject exception(__FILE__, __LINE__);
            std::stringstream errorMessage;
            errorMessage << "File " << GetFileName()
            << " could not be written : error writing buffer.";
            exception.SetDescription(errorMessage.str());
            throw exception;
        }

        // Replace this line :
        // fwrite(buffer, 1, GetImageSizeInBytes(), outputFile);
        // by :
        int written        = 0;
        long long int size =
            ((GetImageSizeInBytes() > 1024 * 1024 * 10) ? (GetImageSizeInBytes() / 10) + 1 : GetImageSizeInBytes());
        try
        {
            while(written < GetImageSizeInBytes())
            {
                UpdateProgress(((float) written) / GetImageSizeInBytes());
                written +=
                    fwrite(((char*) buffer) + written, 1, min(size, GetImageSizeInBytes() - written), outputFile);
            }
        }
        catch(sight::core::Exception& e) // catch progress bar cancel exception
        {
            fclose(outputFile);
            throw;
        }
        UpdateProgress(1.0);
        // End replace

        fclose(outputFile);
    }
}

} // namespace itk
