/*
 *      Copyright (C) 2012-2013 Team XBMC
 *      http://xbmc.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include "PVRChannelGroupInternal.h"

#include <cassert>
#include <utility>

#include "dialogs/GUIDialogOK.h"
#include "epg/EpgContainer.h"
#include "pvr/addons/PVRClients.h"
#include "pvr/PVRDatabase.h"
#include "pvr/PVRManager.h"
#include "pvr/timers/PVRTimers.h"
#include "PVRChannelGroupsContainer.h"
#include "settings/AdvancedSettings.h"
#include "utils/log.h"
#include "utils/Variant.h"

using namespace PVR;
using namespace EPG;

CPVRChannelGroupInternal::CPVRChannelGroupInternal(bool bRadio) :
  m_iHiddenChannels(0)
{
  m_iGroupType = PVR_GROUP_TYPE_INTERNAL;
  m_bRadio = bRadio;
  m_strGroupName = g_localizeStrings.Get(19287);
}

CPVRChannelGroupInternal::CPVRChannelGroupInternal(const CPVRChannelGroup &group) :
    CPVRChannelGroup(group),
    m_iHiddenChannels(group.GetNumHiddenChannels())
{
}

CPVRChannelGroupInternal::~CPVRChannelGroupInternal(void)
{
  Unload();
  g_PVRManager.UnregisterObserver(this);
}

bool CPVRChannelGroupInternal::Load(void)
{
  if (CPVRChannelGroup::Load())
  {
    UpdateChannelPaths();
    g_PVRManager.RegisterObserver(this);

    return true;
  }

  CLog::Log(LOGERROR, "PVRChannelGroupInternal - %s - failed to load channels", __FUNCTION__);
  return false;
}

void CPVRChannelGroupInternal::CheckGroupName(void)
{
  CSingleLock lock(m_critSection);

  /* check whether the group name is still correct, or channels will fail to load after the language setting changed */
  std::string strNewGroupName = g_localizeStrings.Get(19287);
  if (m_strGroupName != strNewGroupName)
  {
    SetGroupName(strNewGroupName, true);
    UpdateChannelPaths();
  }
}

void CPVRChannelGroupInternal::UpdateChannelPaths(void)
{
  CSingleLock lock(m_critSection);
  m_iHiddenChannels = 0;
  for (PVR_CHANNEL_GROUP_MEMBERS::iterator it = m_members.begin(); it != m_members.end(); ++it)
  {
    if (it->second.channel->IsHidden())
      m_iHiddenChannels++;
    else
      it->second.channel->UpdatePath(this);
  }
}

CPVRChannelPtr CPVRChannelGroupInternal::UpdateFromClient(const CPVRChannelPtr &channel, unsigned int iChannelNumber /* = 0 */)
{
  assert(channel.get());

  CSingleLock lock(m_critSection);
  const PVRChannelGroupMember& realChannel(GetByUniqueID(channel->StorageId()));
  if (realChannel.channel)
  {
    realChannel.channel->UpdateFromClient(channel);
    return realChannel.channel;
  }
  else
  {
    PVRChannelGroupMember newMember = { channel, iChannelNumber > 0l ? iChannelNumber : (int)m_sortedMembers.size() + 1 };
    channel->UpdatePath(this);
    m_sortedMembers.push_back(newMember);
    m_members.insert(std::make_pair(channel->StorageId(), newMember));
    m_bChanged = true;

    SortAndRenumber();
  }
  return channel;
}

bool CPVRChannelGroupInternal::Update(void)
{
  CPVRChannelGroupInternal PVRChannels_tmp(m_bRadio);
  PVRChannels_tmp.SetPreventSortAndRenumber();
  return PVRChannels_tmp.LoadFromClients() && UpdateGroupEntries(PVRChannels_tmp);
}

bool CPVRChannelGroupInternal::AddToGroup(const CPVRChannelPtr &channel, int iChannelNumber /* = 0 */)
{
  CSingleLock lock(m_critSection);

  bool bReturn(false);

  /* get the group member, because we need the channel ID in this group, and the channel from this group */
  const PVRChannelGroupMember& groupMember(GetByUniqueID(channel->StorageId()));
  if (!groupMember.channel)
    return bReturn;

  /* switch the hidden flag */
  if (groupMember.channel->IsHidden())
  {
    groupMember.channel->SetHidden(false);
    if (m_iHiddenChannels > 0)
      m_iHiddenChannels--;

    SortAndRenumber();
  }

  /* move this channel and persist */
  bReturn = (iChannelNumber > 0l) ?
    MoveChannel(groupMember.iChannelNumber, iChannelNumber, true) :
    MoveChannel(groupMember.iChannelNumber, m_members.size() - m_iHiddenChannels, true);

  if (m_bLoaded)
    groupMember.channel->Persist();
  return bReturn;
}

bool CPVRChannelGroupInternal::RemoveFromGroup(const CPVRChannelPtr &channel)
{
  CSingleLock lock(m_critSection);
  assert(channel.get());

  if (!IsGroupMember(channel))
    return false;

  /* check if this channel is currently playing if we are hiding it */
  CPVRChannelPtr currentChannel(g_PVRManager.GetCurrentChannel());
  if (currentChannel && currentChannel == channel)
  {
    CGUIDialogOK::ShowAndGetInput(CVariant{19098}, CVariant{19102});
    return false;
  }

  /* switch the hidden flag */
  if (!channel->IsHidden())
  {
    channel->SetHidden(true);
    ++m_iHiddenChannels;
  }
  else
  {
    channel->SetHidden(false);
    if (m_iHiddenChannels > 0)
      --m_iHiddenChannels;
  }

  /* renumber this list */
  SortAndRenumber();

  /* and persist */
  return channel->Persist() &&
      Persist();
}

bool CPVRChannelGroupInternal::MoveChannel(unsigned int iOldChannelNumber, unsigned int iNewChannelNumber, bool bSaveInDb /* = true */)
{
  CSingleLock lock(m_critSection);
  /* new channel number out of range */
  if (iNewChannelNumber > m_members.size() - m_iHiddenChannels)
    iNewChannelNumber = m_members.size() - m_iHiddenChannels;

  return CPVRChannelGroup::MoveChannel(iOldChannelNumber, iNewChannelNumber, bSaveInDb);
}

int CPVRChannelGroupInternal::GetMembers(CFileItemList &results, bool bGroupMembers /* = true */) const
{
  int iOrigSize = results.Size();
  CSingleLock lock(m_critSection);

  for (PVR_CHANNEL_GROUP_SORTED_MEMBERS::const_iterator it = m_sortedMembers.begin(); it != m_sortedMembers.end(); ++it)
    if (bGroupMembers != (*it).channel->IsHidden())
      results.Add(CFileItemPtr(new CFileItem((*it).channel)));

  return results.Size() - iOrigSize;
}

int CPVRChannelGroupInternal::LoadFromDb(bool bCompress /* = false */)
{
  CPVRDatabase *database = GetPVRDatabase();
  if (!database)
    return -1;

  int iChannelCount = Size();

  if (database->Get(*this) > 0)
  {
    if (bCompress)
      database->Compress(true);
  }
  else
  {
    CLog::Log(LOGINFO, "PVRChannelGroupInternal - %s - no channels in the database",
        __FUNCTION__);
  }

  SortByChannelNumber();

  return Size() - iChannelCount;
}

bool CPVRChannelGroupInternal::LoadFromClients(void)
{
  /* get the channels from the backends */
  return g_PVRClients->GetChannels(this) == PVR_ERROR_NO_ERROR;
}

bool CPVRChannelGroupInternal::IsGroupMember(const CPVRChannelPtr &channel) const
{
  return !channel->IsHidden();
}

bool CPVRChannelGroupInternal::AddAndUpdateChannels(const CPVRChannelGroup &channels, bool bUseBackendChannelNumbers)
{
  bool bReturn(false);
  SetPreventSortAndRenumber();

  CSingleLock lock(m_critSection);

  /* go through the channel list and check for updated or new channels */
  for (PVR_CHANNEL_GROUP_MEMBERS::const_iterator it = channels.m_members.begin(); it != channels.m_members.end(); ++it)
  {
    /* check whether this channel is present in this container */
    const PVRChannelGroupMember& existingChannel(GetByUniqueID(it->first));
    if (existingChannel.channel)
    {
      /* if it's present, update the current tag */
      if (existingChannel.channel->UpdateFromClient(it->second.channel))
      {
        bReturn = true;
        CLog::Log(LOGINFO,"PVRChannelGroupInternal - %s - updated %s channel '%s'", __FUNCTION__, m_bRadio ? "radio" : "TV", it->second.channel->ChannelName().c_str());
      }
    }
    else
    {
      /* new channel */
      UpdateFromClient(it->second.channel, bUseBackendChannelNumbers ? it->second.channel->ClientChannelNumber() : 0);
      bReturn = true;
      CLog::Log(LOGINFO,"PVRChannelGroupInternal - %s - added %s channel '%s'", __FUNCTION__, m_bRadio ? "radio" : "TV", it->second.channel->ChannelName().c_str());
    }
  }

  SetPreventSortAndRenumber(false);
  if (m_bChanged)
    SortAndRenumber();

  return bReturn;
}

bool CPVRChannelGroupInternal::UpdateGroupEntries(const CPVRChannelGroup &channels)
{
  bool bReturn(false);

  if (CPVRChannelGroup::UpdateGroupEntries(channels))
  {
    /* try to find channel icons */
    if (g_advancedSettings.m_bPVRChannelIconsAutoScan)
      SearchAndSetChannelIcons();

    g_PVRTimers->UpdateChannels();
    Persist();

    bReturn = true;
  }

  return bReturn;
}

void CPVRChannelGroupInternal::CreateChannelEpg(CPVRChannelPtr channel, bool bForce /* = false */)
{
  if (!channel)
    return;

  CSingleLock lock(channel->m_critSection);
  if (!channel->m_bEPGCreated || bForce)
  {
    CEpgPtr epg = g_EpgContainer.CreateChannelEpg(channel);
    if (epg)
    {
      channel->m_bEPGCreated = true;
      if (epg->EpgID() != channel->m_iEpgId)
      {
        channel->m_iEpgId = epg->EpgID();
        channel->m_bChanged = true;
      }
    }
  }
}

bool CPVRChannelGroupInternal::CreateChannelEpgs(bool bForce /* = false */)
{
  if (!g_EpgContainer.IsStarted())
    return false;
  {
    CSingleLock lock(m_critSection);
    for (PVR_CHANNEL_GROUP_MEMBERS::iterator it = m_members.begin(); it != m_members.end(); ++it)
      CreateChannelEpg(it->second.channel);
  }

  if (HasChangedChannels())
  {
    g_EpgContainer.MarkTablesForPersist();
    return Persist();
  }

  return true;
}

void CPVRChannelGroupInternal::Notify(const Observable &obs, const ObservableMessage msg)
{
  if (msg == ObservableMessageManagerStateChanged)
  {
    g_PVRManager.TriggerEpgsCreate();
  }
}
