//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "io/stream.h"

#include "io/zip/centralDir.h"
#include "io/zip/compressor.h"

#include "memory/safeDelete.h"

namespace Zip
{

//////////////////////////////////////////////////////////////////////////
// CentralDir Class
//////////////////////////////////////////////////////////////////////////

CentralDir::CentralDir()
{
   mHeaderSig = mCentralDirSignature;

   mDiskNumStart = 0;

   mInternalFileAttr = 0;
   mExternalFileAttr = 0;

   mLocalHeadOffset = 0;
   
   mVersionMadeBy = 0;

   mFileComment = NULL;

   mInternalFlags = 0;
}

CentralDir::CentralDir(FileHeader &fh) : FileHeader(fh)
{
   mHeaderSig = mCentralDirSignature;

   mDiskNumStart = 0;

   mInternalFileAttr = 0;
   mExternalFileAttr = 0;

   mLocalHeadOffset = 0;

   mVersionMadeBy = 0;

   mFileComment = NULL;
}

CentralDir::~CentralDir()
{
   SAFE_DELETE_ARRAY(mFileComment);
}

//////////////////////////////////////////////////////////////////////////

bool CentralDir::read(Stream *stream)
{
   stream->read(&mHeaderSig);
   if(mHeaderSig != mCentralDirSignature)
      return false;

   stream->read(&mVersionMadeBy);
   stream->read(&mExtractVer);
   stream->read(&mFlags);
   stream->read(&mCompressMethod);
   stream->read(&mModTime);
   stream->read(&mModDate);
   stream->read(&mCRC32);
   stream->read(&mCompressedSize);
   stream->read(&mUncompressedSize);

   U16 fnLen, efLen, fcLen;
   stream->read(&fnLen);
   stream->read(&efLen);
   stream->read(&fcLen);

   stream->read(&mDiskNumStart);

   stream->read(&mInternalFileAttr);
   stream->read(&mExternalFileAttr);

   stream->read(&mLocalHeadOffset);

   char *fn = new char[fnLen + 1];
   stream->read(fnLen, fn);
   fn[fnLen] = 0;

   SAFE_DELETE_ARRAY(mFilename);
   mFilename = fn;

   // [tom, 10/28/2006] We currently only need the extra fields when we want to
   // open the file, so we won't bother reading them here. This avoids keeping
   // them in memory twice.

   //readExtraFields(stream, efLen);
   stream->setPosition(stream->getPosition() + efLen);

   fn = new char[fcLen + 1];
   stream->read(fcLen, fn);
   fn[fcLen] = 0;

   SAFE_DELETE_ARRAY(mFileComment);
   mFileComment = fn;

   // Sanity checks to make life easier elsewhere
   if(mCompressMethod != Stored && mUncompressedSize == 0 && mCompressedSize == 0)
      mCompressMethod = Stored;

   return true;
}

bool CentralDir::write(Stream *stream)
{
   mHeaderSig = mCentralDirSignature;
   stream->write(mHeaderSig);

   stream->write(mVersionMadeBy);
   stream->write(mExtractVer);
   stream->write(mFlags);
   stream->write(mCompressMethod);
   stream->write(mModTime);
   stream->write(mModDate);
   stream->write(mCRC32);
   stream->write(mCompressedSize);
   stream->write(mUncompressedSize);

   U16 fnLen = mFilename ? (U16)dStrlen(mFilename) : 0,
       efLen = 0,
       fcLen = mFileComment ? (U16)dStrlen(mFileComment) : 0;
   stream->write(fnLen);
   stream->write(efLen);
   stream->write(fcLen);

   stream->write(mDiskNumStart);

   stream->write(mInternalFileAttr);
   stream->write(mExternalFileAttr);

   stream->write(mLocalHeadOffset);

   if(fnLen)
      stream->write(fnLen, mFilename);

   // FIXME [tom, 10/29/2006] Write extra fields here

   if(fcLen)
      stream->write(fcLen, mFileComment);

   return true;
}

//////////////////////////////////////////////////////////////////////////

void CentralDir::setFileComment(const char *comment)
{
   SAFE_DELETE_ARRAY(mFileComment);
   mFileComment = new char [dStrlen(comment)+1];
   dStrcpy((char *)mFileComment, comment);
}

//////////////////////////////////////////////////////////////////////////
// EndOfCentralDir Class
//////////////////////////////////////////////////////////////////////////

EndOfCentralDir::EndOfCentralDir()
{
   mHeaderSig = mEOCDSignature;

   mDiskNum = 0;
   mStartCDDiskNum = 0;
   mNumEntriesInThisCD = 0;
   mTotalEntriesInCD = 0;
   mCDSize = 0;
   mCDOffset = 0;
   mCommentSize = 0;
   mZipComment = NULL;
}

EndOfCentralDir::~EndOfCentralDir()
{
   SAFE_DELETE_ARRAY(mZipComment);
}

//////////////////////////////////////////////////////////////////////////

bool EndOfCentralDir::read(Stream *stream)
{
   stream->read(&mHeaderSig);
   if(mHeaderSig != mEOCDSignature)
      return false;

   stream->read(&mDiskNum);
   stream->read(&mStartCDDiskNum);
   stream->read(&mNumEntriesInThisCD);
   stream->read(&mTotalEntriesInCD);
   stream->read(&mCDSize);
   stream->read(&mCDOffset);

   stream->read(&mCommentSize);
   
   char *comment = new char[mCommentSize + 1];
   stream->read(mCommentSize, comment);
   comment[mCommentSize] = 0;

   SAFE_DELETE_ARRAY(mZipComment);
   mZipComment = comment;

   return true;
}

bool EndOfCentralDir::write(Stream *stream)
{
   stream->write(mHeaderSig);

   stream->write(mDiskNum);
   stream->write(mStartCDDiskNum);
   stream->write(mNumEntriesInThisCD);
   stream->write(mTotalEntriesInCD);
   stream->write(mCDSize);
   stream->write(mCDOffset);

   stream->write(mCommentSize);
   if(mZipComment && mCommentSize)
      stream->write(mCommentSize, mZipComment);

   return true;
}

//////////////////////////////////////////////////////////////////////////

// [tom, 10/19/2006] I know, i know ... this'll get rewritten.
// [tom, 1/23/2007] Maybe.

bool EndOfCentralDir::findInStream(Stream *stream)
{
   U32 initialPos = stream->getPosition();
   U32 size = stream->getStreamSize();
   U32 pos;
   if(size == 0)
      return false;

   if(! stream->setPosition(size - mRecordSize))
      goto hell;

   U32 sig;
   stream->read(&sig);

   if(sig == mEOCDSignature)
   {
      stream->setPosition(size - mRecordSize);
      return true;
   }

   // OK, so we couldn't find the EOCD where we expected it. The zip file
   // either has comments or isn't a zip file. We need to search the last
   // 64Kb of the file for the EOCD.

   pos = size > mEOCDSearchSize ? size - mEOCDSearchSize : 0;
   if(! stream->setPosition(pos))
      goto hell;

   while(pos < (size - 4))
   {
      stream->read(&sig);

      if(sig == mEOCDSignature)
      {
         stream->setPosition(pos);
         return true;
      }

      pos++;
      if(! stream->setPosition(pos))
         goto hell;
   }

hell:
   stream->setPosition(initialPos);
   return false;
}

//////////////////////////////////////////////////////////////////////////

void EndOfCentralDir::setZipComment(U16 commentSize, const char *zipComment)
{
   SAFE_DELETE_ARRAY(mZipComment);
   mZipComment = new char [commentSize];
   dMemcpy((void *)mZipComment, zipComment, commentSize);
   mCommentSize = commentSize;
}

void EndOfCentralDir::setZipComment(const char *zipComment)
{
   setZipComment(dStrlen(zipComment), zipComment);
}

} // end namespace Zip
