// $Id: tag_parse_lyrics3.cpp,v 1.4 2008/01/15 11:20:41 stulleamgym Exp $

// id3lib: a C++ library for creating and manipulating id3v1/v2 tags
// Copyright 1999, 2000  Scott Thomas Haug
// Copyright 2002 Thijmen Klok (thijmen@id3lib.org)

// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Library General Public License as published by
// the Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.
//
// 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 Library General Public
// License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with this library; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

// The id3lib authors encourage improvements and optimisations to be sent to
// the id3lib coordinator.  Please see the README file for details on where to
// send such submissions.  See the AUTHORS file for a list of people who have
// contributed to id3lib.  See the ChangeLog file for a list of changes to
// id3lib.  These files are distributed with id3lib at
// http://download.sourceforge.net/id3lib/

#include <ctype.h>
#include <memory.h>
#include "tag_impl.h" //has <stdio.h> "tag.h" "header_tag.h" "frame.h" "field.h" "spec.h" "id3lib_strings.h" "utils.h"
#include "helpers.h"
#include "id3/io_decorators.h" //has "readers.h" "io_helpers.h" "utils.h"
#include "io_strings.h"

using namespace dami;

namespace
{
  uint32 readIntegerString(ID3_Reader& reader, size_t numBytes)
  {
    uint32 val = 0;
    for (size_t i = 0; i < numBytes && isdigit(reader.peekChar()); ++i)
    {
      val = (val * 10) + (reader.readChar() - '0');
    }
    ID3D_NOTICE( "readIntegerString: val = " << val );
    return val;
  }

  uint32 readIntegerString(ID3_Reader& reader)
  {
    return readIntegerString(reader, reader.remainingBytes());
  }

  bool isTimeStamp(ID3_Reader& reader)
  {
    ID3_Reader::pos_type cur = reader.getCur();
    if (reader.getEnd() < cur + 7)
    {
      return false;
    }
    bool its = ('[' == reader.readChar() &&
                isdigit(reader.readChar()) && isdigit(reader.readChar()) &&
                ':' == reader.readChar() &&
                isdigit(reader.readChar()) && isdigit(reader.readChar()) &&
                ']' == reader.readChar());
    reader.setCur(cur);
    if (its)
    {
      ID3D_NOTICE( "isTimeStamp(): found timestamp, cur = " << reader.getCur() );
    }
    return its;
  }

  uint32 readTimeStamp(ID3_Reader& reader)
  {
    reader.skipChars(1);
    uint32 sec = readIntegerString(reader, 2) * 60;
    reader.skipChars(1);
    sec += readIntegerString(reader, 2);
    reader.skipChars(1);
    ID3D_NOTICE( "readTimeStamp(): timestamp = " << sec );
    return sec * 1000;
  }

  bool findText(ID3_Reader& reader, String text)
  {
    if (text.empty())
    {
      return true;
    }

    size_t index = 0;
    while (!reader.atEnd())
    {
      ID3_Reader::char_type ch = static_cast<ID3_Reader::char_type>(reader.readChar());
      if (ch == text[index])
      {
        index++;
      }
      else if (ch == text[0])
      {
        index = 1;
      }
      else
      {
        index = 0;
      }
      if (index == text.size())
      {
        reader.setCur(reader.getCur() - index);
        ID3D_NOTICE( "findText: found \"" << text << "\" at " <<
                     reader.getCur() );
        break;
      }
    }
    return !reader.atEnd();
  };

  void lyrics3ToSylt(ID3_Reader& reader, ID3_Writer& writer)
  {
    while (!reader.atEnd())
    {
      bool lf = false;
      uint32 ms = 0;
      size_t count = 0;
      while (isTimeStamp(reader))
      {
        // For now, just skip over multiple time stamps
        if (count++ > 0)
        {
          readTimeStamp(reader);
        }
        else
        {
          ms = readTimeStamp(reader);
        }
      }
      while (!reader.atEnd() && !isTimeStamp(reader))
      {
        ID3_Reader::char_type ch = static_cast<ID3_Reader::char_type>(reader.readChar());
        if (0x0A == ch && (reader.atEnd() || isTimeStamp(reader)))
        {
          lf = true;
          break;
        }
        else
        {
          writer.writeChar(ch);
        }
      }

      // put synch identifier
      writer.writeChar('\0');

      // put timestamp
      ID3D_NOTICE( "lyrics3toSylt: ms = " << ms );

      io::writeBENumber(writer, ms, sizeof(uint32));
      if (lf)
      {
        ID3D_NOTICE( "lyrics3toSylt: adding lf" );

        // put the LF
        writer.writeChar(0x0A);
      }
    }
  }
};

bool lyr3::v1::parse(ID3_TagImpl& tag, ID3_Reader& reader)
{
  io::ExitTrigger et(reader);
  ID3_Reader::pos_type end = reader.getCur();
  if (end < reader.getBeg() + 9 + 128)
  {
    ID3D_NOTICE( "id3::v1::parse: bailing, not enough bytes to parse, pos = " << end );
    return false;
  }
  reader.setCur(end - (9 + 128));

  {
    if (io::readText(reader, 9) != "LYRICSEND" ||
        io::readText(reader, 3) != "TAG")
    {
      return false;
    }
  }

  // we have a Lyrics3 v1.00 tag
  if (end < reader.getBeg() + 11 + 9 + 128)
  {
    // the file size isn't large enough to actually hold lyrics
    ID3D_WARNING( "id3::v1::parse: not enough data to parse lyrics3" );
    return false;
  }

  // reserve enough space for lyrics3 + id3v1 tag
  size_t window = end - reader.getBeg();
  size_t lyrDataSize = min<size_t>(window, 11 + 5100 + 9 + 128);
  reader.setCur(end - lyrDataSize);
  io::WindowedReader wr(reader, lyrDataSize - (9 + 128));

  if (!findText(wr, "LYRICSBEGIN"))
  {
    ID3D_WARNING( "id3::v1::parse: couldn't find LYRICSBEGIN, bailing" );
    return false;
  }

  et.setExitPos(wr.getCur());
  wr.skipChars(11);
  wr.setBeg(wr.getCur());

  io::LineFeedReader lfr(wr);
  String lyrics = io::readText(lfr, wr.remainingBytes());
  id3::v2::setLyrics(tag, lyrics, "Converted from Lyrics3 v1.00", "XXX");

  return true;
}

//bool parse(TagImpl& tag, ID3_Reader& reader)
bool lyr3::v2::parse(ID3_TagImpl& tag, ID3_Reader& reader)
{
  io::ExitTrigger et(reader);
  ID3_Reader::pos_type end = reader.getCur();
  if (end < reader.getBeg() + 6 + 9 + 128)
  {
    ID3D_NOTICE( "lyr3::v2::parse: bailing, not enough bytes to parse, pos = " << reader.getCur() );
    return false;
  }

  reader.setCur(end - (6 + 9 + 128));
  uint32 lyrSize = 0;

  ID3_Reader::pos_type beg = reader.getCur();
  lyrSize = readIntegerString(reader, 6);
  if (reader.getCur() < beg + 6)
  {
    ID3D_NOTICE( "lyr3::v2::parse: couldn't find numeric string, lyrSize = " <<
                 lyrSize );
    return false;
  }

  if (io::readText(reader, 9) != "LYRICS200" ||
      io::readText(reader, 3) != "TAG")
  {
    return false;
  }

  if (end < reader.getBeg() + lyrSize + 6 + 9 + 128)
  {
    ID3D_WARNING( "lyr3::v2::parse: not enough data to parse tag, lyrSize = " << lyrSize );
    return false;
  }
  reader.setCur(end - (lyrSize + 6 + 9 + 128));

  io::WindowedReader wr(reader);
  wr.setWindow(wr.getCur(), lyrSize);

  beg = wr.getCur();

  if (io::readText(wr, 11) != "LYRICSBEGIN")
  {
    // not a lyrics v2.00 tag
    ID3D_WARNING( "lyr3::v2::parse: couldn't find LYRICSBEGIN, bailing" );
    return false;
  }

  bool has_time_stamps = false;

  ID3_Frame* lyr_frame = NULL;

  while (!wr.atEnd())
  {
    uint32 fldSize;

    String fldName = io::readText(wr, 3);
    ID3D_NOTICE( "lyr3::v2::parse: fldName = " << fldName );
    fldSize = readIntegerString(wr, 5);
    ID3D_NOTICE( "lyr3::v2::parse: fldSize = " << fldSize );

    String fldData;

    io::WindowedReader wr2(wr, fldSize);
    io::LineFeedReader lfr(wr2);

    fldData = io::readText(lfr, fldSize);
    ID3D_NOTICE( "lyr3::v2::parse: fldData = \"" << fldData << "\"" );

    // the IND field
    if (fldName == "IND")
    {
      has_time_stamps = (fldData.size() > 1 && fldData[1] == '1');
    }

    // the TITLE field
    else if (fldName == "ETT" && !id3::v2::hasTitle(tag))
    {
      //tag.setTitle(fldData);
      id3::v2::setTitle(tag, fldData);
    }

    // the ARTIST field
    else if (fldName == "EAR" && !id3::v2::hasArtist(tag))
    {
      //tag.setArtist(fldData);
      id3::v2::setArtist(tag, fldData);
    }

    // the ALBUM field
    else if (fldName == "EAL" && !id3::v2::hasAlbum(tag))
    {
      //tag.setAlbum(fldData);
      id3::v2::setAlbum(tag, fldData);
    }

    // the Lyrics/Music AUTHOR field
    else if (fldName == "AUT")
    {
      //tag.setAuthor(fldData);
      id3::v2::setLyricist(tag, fldData);
    }

    // the INFORMATION field
    else if (fldName == "INF")
    {
      //tag.setInfo(fldData);
      id3::v2::setComment(tag, fldData, "Lyrics3 v2.00 INF", "XXX");
    }

    // the LYRICS field
    else if (fldName == "LYR")
    {
      // if already found an INF field, use it as description
      String desc =  "Converted from Lyrics3 v2.00";
      //tag.setLyrics(fldData);
      if (!has_time_stamps)
      {
        lyr_frame = id3::v2::setLyrics(tag, fldData, desc, "XXX");
      }
      else
      {
        // converts from lyrics3 to SYLT in-place
        io::StringReader sr(fldData);
        ID3D_NOTICE( "lyr3::v2::parse: determining synced lyrics" );
        BString sylt;
        io::BStringWriter sw(sylt);
        lyrics3ToSylt(sr, sw);

        lyr_frame = id3::v2::setSyncLyrics(tag, sylt, ID3TSF_MS, desc,
                                           "XXX", ID3CT_LYRICS);
        ID3D_NOTICE( "lyr3::v2::parse: determined synced lyrics" );
      }
    }
    else if (fldName == "IMG")
    {
      // currently unsupported
      ID3D_WARNING( "lyr3::v2::parse: IMG field unsupported" );
    }
    else
    {
      ID3D_WARNING( "lyr3::v2::parse: undefined field id: " <<
                    fldName );
    }
  }

  et.setExitPos(beg);
  return true;
}

