/*
 * daap_database.c: The daap database implementation
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: daap_database.c,v 1.13 2005/10/27 10:03:53 cm Exp $
 */

#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <stdlib.h>
#include "daap_database.h"
#include "daap_client.h"

// tell the compiler how we will use your class template
template class cDynamicArray<cDaapHost *>; // used in cDaapHosts
template class cDynamicArray<cDaapDatabase *>; // used in cDaapDatabases
template class cDynamicArray<cDaapPlaylist *>; // used in cDaapPlaylists
template class cDynamicArray<cDaapPlaylistItem *>; // used in cDaapPlaylist
template class cDynamicArray<cMediaDatabaseItem *>; // used in cMediaDatabase

//#include "testers/test.h"
//template class cDynamicArray<cTest *>;

// --- cDynamicArray -----------------------------------------------------------

template <typename ItemType>
cDynamicArray<ItemType>::cDynamicArray(bool ownership) {
  m_ownership = ownership;
  m_numOfItems = 0;
  m_itemBufferSize = 0;
  mp_items = NULL;
}

template <typename ItemType>
cDynamicArray<ItemType>::~cDynamicArray() {
  if (NULL != mp_items) {
    if (m_ownership) {
      for (int i = 0; i < m_numOfItems; i++)
        delete mp_items[i];
    }

    delete[] mp_items;
  }
}

template <typename ItemType>
bool cDynamicArray<ItemType>::Resize(void) {
  ItemType *ptr = NULL;

  ptr = new ItemType[m_itemBufferSize + 32];

  if (NULL != ptr) {
    memset(ptr, 0x00, (m_itemBufferSize + 32) * sizeof(ItemType));
    if (NULL != mp_items);
      memcpy(ptr, mp_items, m_itemBufferSize * sizeof(ItemType));

    delete[] mp_items;
    mp_items = ptr;
    m_itemBufferSize += 32;
  } else
    return false;

  return true;
}

template <typename ItemType>
ItemType cDynamicArray<ItemType>::Item(int index) const {
  if (NULL != mp_items && 0 <= index && index < m_numOfItems)
    return mp_items[index];

  return NULL;
}

// takes ownership of item
template <typename ItemType>
ItemType cDynamicArray<ItemType>::Add(ItemType item) {
  ItemType temp = NULL;

  if (NULL != item) {
    if (m_numOfItems == m_itemBufferSize)
      Resize();

    if (m_numOfItems != m_itemBufferSize) {
      mp_items[m_numOfItems] = item;
      m_numOfItems++;

      temp = item;
    }
  }

  return temp;
}

// returns -1 on error
template <typename ItemType>
int cDynamicArray<ItemType>::Delete(int index) {
  if (NULL != mp_items && 0 <= index && index < m_numOfItems) {
    if (m_ownership)
      delete mp_items[index];
    m_numOfItems--;

    if (index < m_numOfItems)
      memmove(&mp_items[index], &mp_items[index+1], (m_numOfItems - index) * sizeof(ItemType));

    mp_items[m_numOfItems] = NULL;
  } else
    return -1;

  return m_numOfItems;
}

// returns -1 when cannot move item, otherwise new position
template <typename ItemType>
int cDynamicArray<ItemType>::Move(int dest, int src) {
  ItemType temp = NULL;

  if (NULL != mp_items &&  dest != src &&
      0 <= dest && dest < m_numOfItems && 0 <= src && src < m_numOfItems) {
    temp = mp_items[src];

    if (dest < src)
      memmove(&mp_items[dest+1], &mp_items[dest], (src - dest) * sizeof(ItemType));
    else if (dest > src)
      memmove(&mp_items[src], &mp_items[src+1], (dest - src) * sizeof(ItemType));

    mp_items[dest] = temp;
  } else
    return -1;

  return dest;
}

// returns index position when item is found; otherwise -1
template <typename ItemType>
int cDynamicArray<ItemType>::Contains(ItemType item) const {
  for (int i = 0; i < m_numOfItems; i++)
    if (mp_items[i] == item)
      return i;

  return -1;
}

template <typename ItemType>
bool cDynamicArray<ItemType>::Clear(void) {
  if (NULL != mp_items && 0 < m_numOfItems) {
    if (m_ownership) {
      for (int i = 0; i < m_numOfItems; i++)
        delete mp_items[i];
    }

    memset(mp_items, 0x00, m_itemBufferSize * sizeof(ItemType));
    m_numOfItems = 0;
  }

  return true;
}

template <typename ItemType>
void cDynamicArray<ItemType>::Pack(void) {
  if (NULL != mp_items) {
    if (m_numOfItems < m_itemBufferSize && 0 != m_numOfItems) {
      ItemType *temp = new ItemType[m_numOfItems];

      if (NULL != temp) {
        memcpy(temp, mp_items, m_numOfItems * sizeof(ItemType));
        delete[] mp_items;
        mp_items = temp;
        m_itemBufferSize = m_numOfItems;
      }
    }
  }
}

// --- cMediaDatabaseBlob ------------------------------------------------------

cMediaDatabaseBlob::cMediaDatabaseBlob(const uint8_t *data, unsigned long length) : 
  mp_data(data), m_length(length) {
}

cMediaDatabaseBlob::~cMediaDatabaseBlob() {
  delete[] mp_data;
}

// --- cMediaDatabaseGroup -----------------------------------------------------

cMediaDatabaseGroup::cMediaDatabaseGroup(int first, int last) :
  m_first(first), m_last(last) {
}

cMediaDatabaseGroup::~cMediaDatabaseGroup() {
}

// --- cMediaDatabaseItem ------------------------------------------------------

cMediaDatabaseItem::cMediaDatabaseItem() {
}

cMediaDatabaseItem::~cMediaDatabaseItem() {
}

// --- fCmpMediaItem -----------------------------------------------------------

template <eDatabaseField Field>
static int fCmpMediaItem(const void *a, const void *b) {
  const char *x = (*(cMediaDatabaseItem **)a)->GetValue(Field);
  const char *y = (*(cMediaDatabaseItem **)b)->GetValue(Field);

  if (NULL == x)
    return -1;
  else if (NULL == y)
    return 1;

  return strcasecmp(x, y);
}

static int fCmpMediaItem_Id(const void *a, const void *b) {
  return (*(cMediaDatabaseItem **)a)->GetId() - (*(cMediaDatabaseItem **)b)->GetId();
}

// --- cMediaDatabase ----------------------------------------------------------

cMediaDatabaseEngine::cMediaDatabaseEngine(bool ownership) : cDynamicArray<cMediaDatabaseItem *>(ownership) {
  m_sortation = dfNone;
}

cMediaDatabaseEngine::~cMediaDatabaseEngine() {
}

void cMediaDatabaseEngine::SetGroups(eDatabaseField field) {
  if (dfId != field && 0 < m_numOfItems) {
    int first = 0, i = 0;
    const char *p_last = NULL, *p_cur = NULL;

    if (NULL == (p_last = mp_items[i]->GetValue(field)))
      p_last = "";

    mo_groups.Clear();
    mo_groups.Pack();

    for (i = 0; i < m_numOfItems; i++) {
      if (NULL == (p_cur = mp_items[i]->GetValue(field)))
        p_cur = "";

      if (0 != strcasecmp(p_last, p_cur)) {
        mo_groups.Add(new cMediaDatabaseGroup(first, i-1));
        first = i;
        p_last = p_cur;
      }
    }
    // add the last group
    mo_groups.Add(new cMediaDatabaseGroup(first, i-1));
  }
}

void cMediaDatabaseEngine::Sort(eDatabaseField field) {
  if (field != m_sortation && NULL != mp_items && 0 < m_numOfItems) {
    // sort by first column
    switch (field) {
      case dfArtist:
        qsort(mp_items, m_numOfItems, sizeof(cMediaDatabaseItem *), fCmpMediaItem<dfArtist>);
        break;
      case dfAlbum:
        qsort(mp_items, m_numOfItems, sizeof(cMediaDatabaseItem *), fCmpMediaItem<dfAlbum>);
        break;
      case dfGenre:
        qsort(mp_items, m_numOfItems, sizeof(cMediaDatabaseItem *), fCmpMediaItem<dfGenre>);
        break;
      case dfId:
        qsort(mp_items, m_numOfItems, sizeof(cMediaDatabaseItem *), fCmpMediaItem_Id);
        break;
      case dfName:
        qsort(mp_items, m_numOfItems, sizeof(cMediaDatabaseItem *), fCmpMediaItem<dfName>);
        break;
      default:
        break;
    }

    SetGroups(field);

    if (dfId != field) {
      // sort by second column (always dfName)
      int max = mo_groups.Count(), first = 0, last = 0;
      for (int i = 0; i < max; i++) {
        first = mo_groups.Item(i)->GetFirst();
        last = mo_groups.Item(i)->GetLast();
        qsort(mp_items + first, last - first + 1, sizeof(cMediaDatabaseItem *), fCmpMediaItem<dfName>);
      }
    }

#ifdef DEBUG
    printf("%.16s (%d) sort by field=%d\n", __FILE__, __LINE__, field);
#endif
    m_sortation = field;
  }
}

int cMediaDatabaseEngine::BinSearch(eDatabaseField field, const char *key, bool caseSensitive) {
  const char *value = NULL;
  int a = 0, z = m_numOfItems-1, c = 0, r = -1;

  if (NULL != mp_items && 0 < m_numOfItems && field != dfId) {
    if (field != m_sortation)
      Sort(field);

    if (NULL == key)
      key = "";

#ifdef DEBUG
    printf("%.16s (%d) BinSearch key=%s\n", __FILE__, __LINE__, key);
#endif

    while (0 != r && a <= z) {
      c = (a + z) / 2;

      if (NULL == (value = mp_items[c]->GetValue(field)))
        value = "";
      if (caseSensitive)
        r = strcmp(key, value);
      else
        r = strcasecmp(key, value);

      if (0 > r)
        z = c-1;
      else if (0 < r)
        a = c+1;
    }

    if (0 != r)
      c = -1;
  }

  return c;
}

int cMediaDatabaseEngine::BinSearch(eDatabaseField field, char key, bool caseSensitive) {
  int a = 0, z = m_numOfItems-1, c = 0;
  char value = 0x00;

  if (NULL != mp_items && 0 < m_numOfItems && field != dfId) {
    if (field != m_sortation)
      Sort(field);

    if (! caseSensitive)
      key = tolower(key);

    while (key != value && a <= z) {
      c = (a + z) / 2;

      if (NULL == mp_items[c]->GetValue(field))
        value = '0';
      else
        value = mp_items[c]->GetValue(field)[0];

      if (! caseSensitive)
        value = tolower(value);

      if (key < value)
        z = c-1;
      else if (key > value)
        a = c+1;
    }

    if (key != value)
      c = -1;
  }

  return c;
}

int cMediaDatabaseEngine::BinSearch(eDatabaseField field, int key) {
  int a = 0, c = 0, z = m_numOfItems-1, value = -1;

  if (NULL != mp_items && 0 < m_numOfItems && field == dfId) {
    if (field != m_sortation)
      Sort(field);

    while (key != value && a <= z) {
      c = (a + z) / 2;

      value = mp_items[c]->GetId();
      if (key < value)
        z = c-1;
      else if (key > value)
        a = c+1;
    }

    if (key != value) {
#ifdef DEBUG
      printf("%.16s (%d) search index %d:%d\n", __FILE__, __LINE__, key, value);
#endif
      c = -1;
    }
  }

  return c;
}
