/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2023 Osimis S.A., Belgium
 * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 *
 * This program 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.
 *
 * This program 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 this program. If not, see
 * <http://www.gnu.org/licenses/>.
 **/


#include "../PrecompiledHeaders.h"
#include "DicomSequentialReader.h"

#include "../DicomParsing/ParsedDicomFile.h"
#include "../OrthancException.h"
#include "../StringMemoryBuffer.h"
#include "BaseDataIdentifier.h"
#include "DataSourceReader.h"
#include "DataSourceSequentialReader.h"
#include "DicomDataSource.h"
#include "StorageAreaDataSource.h"
#include "TranscoderDataSource.h"

#include <dcmtk/dcmdata/dcfilefo.h>


namespace Orthanc
{
  DicomSequentialReader::Item::Item(ParsedDicomFile* parsed) :
    parsed_(parsed)
  {
    if (parsed == NULL)
    {
      throw OrthancException(ErrorCode_NullPointer);
    }
  }


  DicomSequentialReader::Item::Item(const boost::shared_ptr<IMemoryBuffer>& raw) :
    raw_(raw)
  {
    if (raw.get() == NULL)
    {
      throw OrthancException(ErrorCode_NullPointer);
    }
  }


  DicomSequentialReader::Item::Item(ParsedDicomFile* parsed /* takes ownership */,
                                    IMemoryBuffer* raw /* takes ownership */) :
    parsed_(parsed),
    raw_(raw)
  {
    if (parsed == NULL ||
        raw == NULL)
    {
      throw OrthancException(ErrorCode_NullPointer);
    }
  }


  ParsedDicomFile& DicomSequentialReader::Item::GetParsedDicomFile()
  {
    if (parsed_.get() != NULL)
    {
      return *parsed_;
    }
    else if (raw_.get() != NULL)
    {
      parsed_.reset(new ParsedDicomFile(raw_->GetData(), raw_->GetSize()));
      return *parsed_;
    }
    else
    {
      throw OrthancException(ErrorCode_InternalError);
    }
  }


  const IMemoryBuffer& DicomSequentialReader::Item::GetRawMemoryBuffer()
  {
    if (raw_.get() != NULL)
    {
      return *raw_;
    }
    else if (parsed_.get() != NULL)
    {
      std::string s;
      parsed_->SaveToMemoryBuffer(s);

      raw_.reset(StringMemoryBuffer::CreateFromSwap(s));
      return *raw_;
    }
    else
    {
      throw OrthancException(ErrorCode_InternalError);
    }
  }


  void DicomSequentialReader::Item::SetUserData(IDynamicObject* userData /* takes ownership */)
  {
    if (userData == NULL)
    {
      throw OrthancException(ErrorCode_NullPointer);
    }
    else
    {
      userData_.reset(userData);
    }
  }


  const IDynamicObject& DicomSequentialReader::Item::GetUserData() const
  {
    if (userData_.get() == NULL)
    {
      throw OrthancException(ErrorCode_BadSequenceOfCalls);
    }
    else
    {
      return *userData_;
    }
  }


  class DicomSequentialReader::Disconnector : public DataSourceSequentialReader::IValueDisconnector
  {
  private:
    SourceType  sourceType_;

  public:
    explicit Disconnector(SourceType sourceType) :
      sourceType_(sourceType)
    {
    }

    virtual IDynamicObject* Apply(DataSourceAnswer::Item* source) ORTHANC_OVERRIDE
    {
      std::unique_ptr<DataSourceAnswer::Item> protection(source);

      switch (sourceType_)
      {
        case SourceType_StorageArea:
        {
          std::unique_ptr<StorageAreaDataSource::Range> range(new StorageAreaDataSource::Range(protection.release()));
          return new Item(range->GetBuffer());  // The buffer is shared through the cache
        }

        case SourceType_Dicom:
        {
          std::unique_ptr<DicomDataSource::Dicom> dicom(new DicomDataSource::Dicom(protection.release()));
          return new Item(dicom->Clone());
        }

        case SourceType_Transcoder:
        {
          std::unique_ptr<TranscoderDataSource::Transcoded> dicom(new TranscoderDataSource::Transcoded(protection.release()));
          TranscoderDataSource::Transcoded::LockAsParsed lock(*dicom);
          return new Item(lock.GetContent().Clone(true));
        }

        default:
          throw OrthancException(ErrorCode_InternalError);
      }
    }
  };


  DicomSequentialReader::DicomSequentialReader(SourceType sourceType,
                                               const boost::shared_ptr<IExecutorService>& executor,
                                               DicomTransferSyntax targetSyntax,
                                               TranscodingSopInstanceUidMode mode,
                                               bool hasLossyQuality,
                                               unsigned int lossyQuality,
                                               const boost::shared_ptr<DataSourceReader>& reader,
                                               unsigned int windowSize,
                                               uint64_t windowCapacity) :
    sourceType_(sourceType),
    targetSyntax_(targetSyntax),
    mode_(mode),
    hasLossyQuality_(hasLossyQuality),
    lossyQuality_(lossyQuality),
    reader_(new DataSourceSequentialReader(executor, reader, new Disconnector(sourceType), windowSize, windowCapacity))
  {
  }


  void DicomSequentialReader::SubmitInternal(const FileInfo& attachment,
                                             IDynamicObject* userData)
  {
    std::unique_ptr<IDynamicObject> protection(userData);

    std::unique_ptr<IDataIdentifier> id;

    switch (sourceType_)
    {
      case SourceType_StorageArea:
        id.reset(StorageAreaDataSource::CreateAttachmentRequest(attachment, true /* uncompress */));
        break;

      case SourceType_Dicom:
        id.reset(DicomDataSource::CreateWholeRequest(attachment));
        break;

      case SourceType_Transcoder:
        id.reset(TranscoderDataSource::CreateRequest(attachment, targetSyntax_, mode_, hasLossyQuality_, lossyQuality_));
        break;

      default:
        throw OrthancException(ErrorCode_InternalError);
    }

    if (protection.get() != NULL)
    {
      dynamic_cast<BaseDataIdentifier&>(*id).SetUserData(protection.release());
    }

    reader_->Submit(id.release());
  }


  void DicomSequentialReader::Submit(const FileInfo& attachment)
  {
    SubmitInternal(attachment, NULL);
  }


  void DicomSequentialReader::Submit(const FileInfo& attachment,
                                     IDynamicObject* userData)
  {
    if (userData == NULL)
    {
      throw OrthancException(ErrorCode_NullPointer);
    }

    SubmitInternal(attachment, userData);
  }


  void DicomSequentialReader::Start()
  {
    reader_->Start();
  }


  bool DicomSequentialReader::HasNext()
  {
    return reader_->HasNext();
  }


  DicomSequentialReader::Item* DicomSequentialReader::Next()
  {
    std::unique_ptr<DataSourceSequentialReader::Item> source(reader_->Next());

    std::unique_ptr<Item> target(dynamic_cast<Item*>(source->ReleaseValue()));

    if (source->HasUserData())
    {
      target->SetUserData(source->ReleaseUserData());
    }

    return target.release();
  }


  DicomSequentialReader::Factory::Factory(const boost::shared_ptr<IExecutorService>& executor,
                                          const boost::shared_ptr<DataSourceReader>& storageAreaReader,
                                          const boost::shared_ptr<DataSourceReader>& dicomReader,
                                          const boost::shared_ptr<DataSourceReader>& transcoderReader,
                                          unsigned int windowSize,
                                          uint64_t windowCapacity) :
    executor_(executor),
    storageAreaReader_(storageAreaReader),
    dicomReader_(dicomReader),
    transcoderReader_(transcoderReader),
    windowSize_(windowSize),
    windowCapacity_(windowCapacity)
  {
    if (!executor)
    {
      throw OrthancException(ErrorCode_NullPointer);
    }

    if (windowSize == 0)
    {
      throw OrthancException(ErrorCode_ParameterOutOfRange);
    }
  }


  DicomSequentialReader* DicomSequentialReader::Factory::CreateForOriginalRawMemoryBuffer() const
  {
    return new DicomSequentialReader(
      DicomSequentialReader::SourceType_StorageArea, executor_,
      DicomTransferSyntax_LittleEndianImplicit, TranscodingSopInstanceUidMode_AllowNew,  /* dummy values */
      false /* no lossy */, 0 /* unused lossy quality */,
      storageAreaReader_, windowSize_, windowCapacity_);
  }


  DicomSequentialReader* DicomSequentialReader::Factory::CreateForTranscodedRawMemoryBuffer(DicomTransferSyntax targetSyntax,
                                                                                            TranscodingSopInstanceUidMode mode,
                                                                                            bool hasLossyQuality,
                                                                                            unsigned int lossyQuality) const
  {
    // No optimization seems possible, we need to go through the transcoder anyway
    return CreateForTranscodedParsedDicomFile(targetSyntax, mode, hasLossyQuality, lossyQuality);
  }


  DicomSequentialReader* DicomSequentialReader::Factory::CreateForOriginalParsedDicomFile() const
  {
    return new DicomSequentialReader(
      DicomSequentialReader::SourceType_Dicom, executor_,
      DicomTransferSyntax_LittleEndianImplicit, TranscodingSopInstanceUidMode_AllowNew,  /* dummy values */
      false /* no lossy */, 0 /* unused lossy quality */,
      dicomReader_, windowSize_, windowCapacity_);
  }


  DicomSequentialReader* DicomSequentialReader::Factory::CreateForTranscodedParsedDicomFile(DicomTransferSyntax targetSyntax,
                                                                                            TranscodingSopInstanceUidMode mode,
                                                                                            bool hasLossyQuality,
                                                                                            unsigned int lossyQuality) const
  {
    return new DicomSequentialReader(
      DicomSequentialReader::SourceType_Transcoder, executor_,
      targetSyntax, mode, hasLossyQuality, lossyQuality,
      transcoderReader_, windowSize_, windowCapacity_);
  }
}
