/***************************************************************************
    copyright           : (C) 2011 by Mathias Panzenböck
    email               : grosser.meister.morti@gmx.net
 ***************************************************************************/

/***************************************************************************
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License version   *
 *   2.1 as published by the Free Software Foundation.                     *
 *                                                                         *
 *   This library 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 library; if not, write to the Free Software   *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
 *   02110-1301  USA                                                       *
 *                                                                         *
 *   Alternatively, this file is available under the Mozilla Public        *
 *   License Version 1.1.  You may obtain a copy of the License at         *
 *   http://www.mozilla.org/MPL/                                           *
 ***************************************************************************/


#include "s3mfile.h"

#include "tstringlist.h"
#include "tdebug.h"
#include "tpropertymap.h"
#include "modfileprivate.h"

using namespace TagLib;
using namespace S3M;

class S3M::File::FilePrivate
{
public:
  FilePrivate(AudioProperties::ReadStyle propertiesStyle)
    : properties(propertiesStyle)
  {
  }

  Mod::Tag        tag;
  S3M::Properties properties;
};

S3M::File::File(FileName file, bool readProperties,
                AudioProperties::ReadStyle propertiesStyle) :
  Mod::FileBase(file),
  d(std::make_unique<FilePrivate>(propertiesStyle))
{
  if(isOpen())
    read(readProperties);
}

S3M::File::File(IOStream *stream, bool readProperties,
                AudioProperties::ReadStyle propertiesStyle) :
  Mod::FileBase(stream),
  d(std::make_unique<FilePrivate>(propertiesStyle))
{
  if(isOpen())
    read(readProperties);
}

S3M::File::~File() = default;

Mod::Tag *S3M::File::tag() const
{
  return &d->tag;
}

PropertyMap S3M::File::properties() const
{
  return d->tag.properties();
}

PropertyMap S3M::File::setProperties(const PropertyMap &properties)
{
  return d->tag.setProperties(properties);
}

S3M::Properties *S3M::File::audioProperties() const
{
  return &d->properties;
}

bool S3M::File::save()
{
  if(readOnly()) {
    debug("S3M::File::save() - Cannot save to a read only file.");
    return false;
  }
  // note: if title starts with "Extended Module: "
  // the file would look like an .xm file
  seek(0);
  writeString(d->tag.title(), 27);
  // string terminating NUL is not optional:
  writeByte(0);

  seek(32);

  unsigned short length = 0;
  unsigned short sampleCount = 0;

  if(!readU16L(length) || !readU16L(sampleCount))
    return false;

  seek(28, Current);

  int channels = 0;
  for(int i = 0; i < 32; ++ i) {
    unsigned char setting = 0;
    if(!readByte(setting))
      return false;
    // or if(setting >= 128)?
    // or channels = i + 1;?
    // need a better spec!
    if(setting != 0xff) ++ channels;
  }

  seek(channels, Current);

  StringList lines = d->tag.comment().split("\n");
  // write comment as sample names:
  for(unsigned short i = 0; i < sampleCount; ++ i) {
    seek(96L + length + (static_cast<long>(i) << 1));

    unsigned short instrumentOffset = 0;
    if(!readU16L(instrumentOffset))
      return false;
    seek((static_cast<long>(instrumentOffset) << 4) + 48);

    if(i < lines.size())
      writeString(lines[i], 27);
    else
      writeString(String(), 27);
    // string terminating NUL is not optional:
    writeByte(0);
  }
  return true;
}

void S3M::File::read(bool)
{
  if(!isOpen())
    return;

  READ_STRING(d->tag.setTitle, 28);
  READ_BYTE_AS(mark);
  READ_BYTE_AS(type);

  READ_ASSERT(mark == 0x1A && type == 0x10);

  seek(32);

  READ_U16L_AS(length);
  READ_U16L_AS(sampleCount);

  d->properties.setSampleCount(sampleCount);

  READ_U16L(d->properties.setPatternCount);
  READ_U16L(d->properties.setFlags);
  READ_U16L(d->properties.setTrackerVersion);
  READ_U16L(d->properties.setFileFormatVersion);

  READ_ASSERT(readBlock(4) == "SCRM");

  READ_BYTE(d->properties.setGlobalVolume);
  READ_BYTE(d->properties.setBpmSpeed);
  READ_BYTE(d->properties.setTempo);

  READ_BYTE_AS(masterVolume);
  d->properties.setMasterVolume(masterVolume & 0x7f);
  d->properties.setStereo((masterVolume & 0x80) != 0);

  // I've seen players who call the next two bytes
  // "ultra click" and "use panning values" (if == 0xFC).
  // I don't see them in any spec, though.
  // Hm, but there is "UltraClick-removal" and some other
  // variables in ScreamTracker III's GUI.

  seek(12, Current);

  int channels = 0;
  for(int i = 0; i < 32; ++ i) {
    READ_BYTE_AS(setting);
    // or if(setting >= 128)?
    // or channels = i + 1;?
    // need a better spec!
    if(setting != 0xff) ++ channels;
  }
  d->properties.setChannels(channels);

  seek(96);
  unsigned short realLength = 0;
  for(unsigned short i = 0; i < length; ++ i) {
    READ_BYTE_AS(order);
    if(order == 255) break;
    if(order != 254) ++ realLength;
  }
  d->properties.setLengthInPatterns(realLength);

  seek(channels, Current);

  // Note: The S3M spec mentions samples and instruments, but in
  //       the header there are only pointers to instruments.
  //       However, there I never found instruments (SCRI) but
  //       instead samples (SCRS).
  StringList comment;
  for(unsigned short i = 0; i < sampleCount; ++ i) {
    seek(96L + length + (static_cast<long>(i) << 1));

    READ_U16L_AS(sampleHeaderOffset);
    seek(static_cast<long>(sampleHeaderOffset) << 4);

    READ_BYTE_AS(sampleType);
    READ_STRING_AS(dosFileName, 13);
    READ_U16L_AS(sampleDataOffset);
    READ_U32L_AS(sampleLength);
    READ_U32L_AS(repeatStart);
    READ_U32L_AS(repeatStop);
    READ_BYTE_AS(sampleVolume);

    seek(1, Current);

    READ_BYTE_AS(packing);
    READ_BYTE_AS(sampleFlags);
    READ_U32L_AS(baseFrequency);

    seek(12, Current);

    READ_STRING_AS(sampleName, 28);
    // The next 4 bytes should be "SCRS", but I've found
    // files that are otherwise ok with 4 nils instead.
    // READ_ASSERT(readBlock(4) == "SCRS");

    comment.append(sampleName);
  }

  d->tag.setComment(comment.toString("\n"));
  d->tag.setTrackerName("ScreamTracker III");
}
