/*****************************************************************************
Open Source MIT License

Copyright (c) 2017 Matthew Ahmed

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 "Headers.h"
#include "ZWin.h"
#include "songarray.h"

CSongArray::CSongArray(ZWin * pOwner)
{
  SetOwner(pOwner);
  ClearError();

//  m_nMaxInd = RAND_MAX;
  m_nMaxInd = INITIAL_ARRAY_SIZE - 1;
  m_sspSongs = NULL;
  m_sspSongs = new SongStruct[INITIAL_ARRAY_SIZE];
  if (!m_sspSongs)
  {
    SetError(_T("Error allocating memory for song structure array"));
    m_nMaxInd = -1; // Force error return with this msg on first attempt to use
  }

  // Won't enter this loop if nMaxInd = -1
  for (INT i = 0; i <= m_nMaxInd; i++) m_sspSongs[i].sLine = NULL;

  m_nNextUnusedInd = 0;
  m_nListLength = 0;
  m_nMaxSongCount = 0;

}

CSongArray::~CSongArray(void)
{
  ClearArray();
  if (m_sspSongs) delete [] m_sspSongs;
}

void CSongArray::SetOwner(ZWin * pOwner)
{
  m_pOwner = pOwner;
  m_hDlg = pOwner ? pOwner->GetWnd() : NULL;
}

void CSongArray::ClearArray(void)
{
  if (m_sspSongs)
  {
    for (INT i = 0; i < m_nNextUnusedInd; i++)
    {
      if (m_sspSongs[i].sLine)
      {
	      delete [] m_sspSongs[i].sLine;
	      m_sspSongs[i].sLine = NULL;
      }
    }
  }
  m_nNextUnusedInd = 0;
  m_nListLength = 0;
  m_nMaxSongCount = 0;
}

BOOL CSongArray::DoubleArraySize()
{
  // Reallocate m_sspSongs with twice as many SongStruct elements
  // First need to copy existing elements to a temp array
  // Should set initial size large enough to avoid need for this wasted time
  INT nOldSize = m_nMaxInd + 1;
  INT nNewSize = 2 * nOldSize;
  TCHAR sErrAllocateFor[1024]; // Same text part of all possible error messages
  _stprintf(sErrAllocateFor,
            _T("Error while increasing song array size to %d\r\n")
               _T("Error allocating memory for"),
            nNewSize);

  SongStruct * sspSongsTemp = new SongStruct[nOldSize];
  if (!sspSongsTemp)
  {
    _stprintf(m_sError, _T("%s temp song array, size %d"),
              sErrAllocateFor, nOldSize);
    return FALSE;
  }

  // Copy all songs from main array to temp array
  for (INT i = 0; i < nOldSize; i++)
  {
    sspSongsTemp[i].sLine = new TCHAR[_tcslen(m_sspSongs[i].sLine) + 8];
    if (!sspSongsTemp[i].sLine)
    {
      _stprintf(m_sError, _T("%s temp song array line,\r\n'%s'"),
                sErrAllocateFor, m_sspSongs[i].sLine);
      return FALSE;
    }
    _tcscpy(sspSongsTemp[i].sLine, m_sspSongs[i].sLine);
    sspSongsTemp[i].nCount = m_sspSongs[i].nCount;

    delete [] m_sspSongs[i].sLine;
    m_sspSongs[i].sLine = NULL;
  } // for (INT i = 0; i < nOldSize; i++)

  // Delete main array and reallocate at twice the size
  delete [] m_sspSongs;
  m_sspSongs  = new SongStruct[nNewSize];
  if (!m_sspSongs)
  {
    _stprintf(m_sError, _T("%s replacement array, size %d"),
              sErrAllocateFor, nNewSize);
    return FALSE;
  }

  // Restore original contents from temp array
  for (INT i = 0; i < nOldSize; i++)
  {
    m_sspSongs[i].sLine = new TCHAR[_tcslen(sspSongsTemp[i].sLine) + 8];
    if (!m_sspSongs[i].sLine)
    {
      _stprintf(m_sError, _T("%s restored array line,\r\n'%s'"),
                sErrAllocateFor, sspSongsTemp[i].sLine);
      return FALSE;
    }
    _tcscpy(m_sspSongs[i].sLine, sspSongsTemp[i].sLine);
    m_sspSongs[i].nCount = sspSongsTemp[i].nCount;

    delete [] sspSongsTemp[i].sLine;
  } // for (INT i = 0; i < nOldSize; i++)
  delete [] sspSongsTemp;

  m_nMaxInd = nNewSize - 1;
  return TRUE;
} // BOOL CSongArray::DoubleArraySize()

// This adds song in next array member regardless whether song already in array.
// Called by CSongArray::IncrementOrAddSong when song not already in array.
// Also called directly from CMinDlg for "more truly random" shuffle, where each
// repeat occurrence of a song is separate array member with nOccurrences = 1.
INT CSongArray::AddSong(LPCTSTR sListLine, INT nOccurrences)
{
  if (m_nNextUnusedInd > m_nMaxInd)
  {
//DWORD dwStart = GetTickCount();
    if (m_nMaxInd == -1) return FALSE; // Error allocating in constructor

    // More normal case, used all slots in the array, need to add more
    if (!DoubleArraySize()) return FALSE; // m_sError already set
    // Proceed as normal, nMaxInd is now twice as large
//DWORD dwEnd = GetTickCount();
//_stprintf(sBS, _T("Song array size increased to %d\nin %d milliseconds"),
//          (m_nMaxInd + 1), (INT) (dwEnd - dwStart));
//INT nRet = ::MessageBox(m_hDlg, sBS, _T("CSongArray"), MB_OK | MB_ICONINFORMATION);
  }

  if (nOccurrences > SONG_MAX_REPEATS)
  {
    _stprintf(m_sError,
             _T("Number of song occurrences %d greater than max allowed %d\r\n%s"),
              nOccurrences, SONG_MAX_REPEATS, sListLine);
    return FALSE;
  }

  if (!nOccurrences)
    return SetError(_T("Attempted to add song with 0 occurrences"));

  if (!sListLine || !sListLine[0])
    return SetError(_T("Null or empty song file parameter"));

                                      // Only need +1, but allow utf miscalc
  m_sspSongs[m_nNextUnusedInd].sLine = new TCHAR[_tcslen(sListLine) + 8]; //16];
  if (!m_sspSongs[m_nNextUnusedInd].sLine)
  {
    _stprintf(m_sError,
             _T("Error allocating memory for playlist line (array index %d)"),
              m_nNextUnusedInd);
    return FALSE;
  }

  _tcscpy(m_sspSongs[m_nNextUnusedInd].sLine, sListLine);
  m_sspSongs[m_nNextUnusedInd].nCount = nOccurrences;
  m_nMaxSongCount = max(m_nMaxSongCount, nOccurrences);
  m_nListLength += nOccurrences;

  // Store array index as value in hash to look up by sListLine (i.e. path) as key.
  // IF not checking for song already in array, this may overwrite earlier value for
  // same song key; then hash lookup shows last index entered (e.g. to test exists).
//  m_path_arrix_hash[sListLine] = m_nNextUnusedInd;
  m_path_arrix_hash[m_sspSongs[m_nNextUnusedInd].sLine] = m_nNextUnusedInd;

  m_nNextUnusedInd ++;

	return nOccurrences;
} // INT CSongArray::AddSong(LPCTSTR sListLine, INT nOccurrences)

INT CSongArray::IncrementSong(INT nSongIndex, LPCTSTR sPath, INT nOccurrences)
{
  if ((m_sspSongs[nSongIndex].nCount + nOccurrences) > SONG_MAX_REPEATS)
  {
    _stprintf(m_sError,
              _T("Attempt to add %d repetitions for song\r\n")
              _T("%s\r\nwould raise total repetitions to %d\r\n")
              _T( "Maximum allowed is %d"),
             nOccurrences, sPath,
             (m_sspSongs[nSongIndex].nCount + nOccurrences), SONG_MAX_REPEATS);
    return FALSE;
  }

  m_sspSongs[nSongIndex].nCount += nOccurrences;
  m_nListLength += nOccurrences;
  m_nMaxSongCount = max(m_nMaxSongCount, m_sspSongs[nSongIndex].nCount);
  return m_sspSongs[nSongIndex].nCount;
} // INT CSongArray::IncrementSong(INT nSongIndex, INT nOccurrences = 1)

INT CSongArray::IncrementOrAddSong(LPCTSTR sListLine, INT nOccurrences) 
{
  if (!nOccurrences)
    return SetError(_T("Attempted to increment song by 0 occurrences"));

  INT nIndex = GetSongIndex(sListLine);
  if (nIndex == -1) return AddSong(sListLine, nOccurrences);
  else return IncrementSong(nIndex, sListLine, nOccurrences);

// v.3 Move inlined below to new function IncrementSong, so that can be
// callled directly when we already know the song is in the array.

  //if ((m_sspSongs[nIndex].nCount + nOccurrences) > SONG_MAX_REPEATS)
  //{
  //  _stprintf(m_sError,
  //           _T("Attempt to add %d repetitions for song\r\n")
  //              _T("%swould raise total repetitions to %d\r\n")
  //             _T( "Maximum allowed is %d"),
  //           nOccurrences, sListLine,
  //           (m_sspSongs[nIndex].nCount + nOccurrences), SONG_MAX_REPEATS);
  //  return FALSE;
  //}

  //m_sspSongs[nIndex].nCount += nOccurrences;
  //m_nListLength += nOccurrences;
  //m_nMaxSongCount = max(m_nMaxSongCount, m_sspSongs[nIndex].nCount);
  //return m_sspSongs[nIndex].nCount;

} // INT CSongArray::IncrementOrAddSong(LPCTSTR sListLine, INT nOccurrences) 

/*
INT CSongArray::GetSongIndex(LPCTSTR sListLine)
// N.B. If nonunique SongStruct.sLine's, only tests existence
{
  for (INT i = 0; i < m_nNextUnusedInd; i++)
  {
    if (!_tcscmp(sListLine, m_sspSongs[i].sLine))
      return i;
  }
  return -1;
}
*/

// Replace brute force lookup above with hash
// N.B. If nonunique SongStruct.sLine's, only tests existence
INT CSongArray::GetSongIndex(LPCTSTR sListLine)
{
  m_path_arrix_iter = m_path_arrix_hash.find(sListLine);

  if (m_path_arrix_iter == m_path_arrix_hash.end())
    return -1;

  // Iterator for this hash is pair of LPCTSTR, INT
  return m_path_arrix_iter->second;
}

LPCTSTR CSongArray::GetSong(INT nArrayIndex)
{
  if (nArrayIndex < m_nNextUnusedInd) return m_sspSongs[nArrayIndex].sLine;
  else return NULL;
}

INT CSongArray::GetCountOneSong(INT nArrayIndex) 
{
  if (nArrayIndex < m_nNextUnusedInd) return m_sspSongs[nArrayIndex].nCount;
  else return 0;
}

BOOL CSongArray::SetCountOneSong(INT nArrayIndex, INT nOccurrences)
{
  if (nArrayIndex < m_nNextUnusedInd)
  {
    m_sspSongs[nArrayIndex].nCount = nOccurrences;
    return TRUE;
  }
  else
  {
    _stprintf(m_sError,
              _T("Attempted to set frequency for non-existent song index: %d\r\n"),
              nArrayIndex);
    return FALSE;
  }

}

INT CSongArray::GetSize(void)
{
  return m_nNextUnusedInd; // Count of used members, NOT max used index
}

INT CSongArray::GetListLength(void)
{
  return m_nListLength; // Counts each song * occurrences
}

INT CSongArray::GetMaxOccurrences(void)
{
  return m_nMaxSongCount;
}

BOOL CSongArray::SongsTheSame(INT nSong1, INT nSong2)
{
  return _tcscmp(m_sspSongs[nSong1].sLine, m_sspSongs[nSong2].sLine) ? FALSE : TRUE;
}

BOOL CSongArray::CopyToAnother(CSongArray * psaTarget)
{
  psaTarget->ClearArray();
  LPCTSTR sErrIntro = _T("CSongArray::CopyToAnother error");

  // All songs will have the same index number in target as in source
  for (INT i = 0; i < m_nNextUnusedInd; i++)
  {
    if (!psaTarget->AddSong(m_sspSongs[i].sLine, m_sspSongs[i].nCount))
    {
      _stprintf(sBS, _T("Error copying song to target\r\n%s"), psaTarget->GetError());
      return SetError(sErrIntro, sBS);
    }
  }

  // Done. Reality checks.
  if (m_nNextUnusedInd != psaTarget->m_nNextUnusedInd)
    return SetError(sErrIntro,
                    _T("Song counts differ between source and target after copy"));

  if (m_nListLength != psaTarget->m_nListLength)
    return SetError(sErrIntro,
                    _T("List lengths differ between source and target after copy"));

  if (m_nMaxSongCount != psaTarget->m_nMaxSongCount)
    return SetError(sErrIntro,
                    _T("Max repeat counts differ between source and target after copy"));

  return TRUE;
}

// Error reporting functions copied from CSettings
BOOL CSongArray::SetError(LPCTSTR sErr1, LPCTSTR sErr2)
{
  _stprintf(m_sError, _T("SongArray class error:\r\n%s%s%s"), sErr1,
											sErr2 ? _T("\r\n") : _T(""), sErr2 ? sErr2 : _T(""));
	return FALSE;
}

//BOOL CSongArray::AddToError(LPCTSTR sErr)
//{
//	_tcscat(m_sError, _T("\r\n"));
//	_tcscat(m_sError, sErr);
//	return FALSE;
//}

LPCTSTR CSongArray::GetError(BOOL bClear) // Changed from CSettings
{
  _tcscpy(m_sReportError, m_sError);
	if (bClear) ClearError();
  return m_sReportError;
}

void CSongArray::ClearError()
{
	_tcscpy(m_sError, _T("No error recorded in SongArray class"));
}

BOOL CSongArray::ShowError(LPCTSTR sCapt, LPCTSTR sPre, LPCTSTR sPost)
{
	return fnError(m_hDlg, sCapt, sPre, m_sError, sPost);
}