/*
 *  Copyright (C) 2012-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#pragma once

#include "XBDateTime.h"

#include <functional>
#include <map>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>

class CFileItem;
class CFileItemList;

namespace PVR
{
  struct GridItem
  {
    GridItem(const std::shared_ptr<CFileItem>& _item, float _width, int _startBlock, int _endBlock)
      : item(_item),
        originWidth(_width),
        width(_width),
        startBlock(_startBlock),
        endBlock(_endBlock)
    {
    }

    bool operator==(const GridItem& other) const
    {
      return (startBlock == other.startBlock && endBlock == other.endBlock);
    }

    std::shared_ptr<CFileItem> item;
    float originWidth = 0.0f;
    float width = 0.0f;
    int startBlock = 0;
    int endBlock = 0;
  };

  class CPVREpgInfoTag;

  class CGUIEPGGridContainerModel
  {
  public:
    static constexpr int MINSPERBLOCK = 5; // minutes

    CGUIEPGGridContainerModel() = default;
    virtual ~CGUIEPGGridContainerModel() = default;

    void Initialize(const std::unique_ptr<CFileItemList>& items,
                    const CDateTime& gridStart,
                    const CDateTime& gridEnd,
                    int iFirstChannel,
                    int iChannelsPerPage,
                    int iFirstBlock,
                    int iBlocksPerPage,
                    int iRulerUnit,
                    float fBlockSize);
    void SetInvalid();

    static const int INVALID_INDEX = -1;
    void FindChannelAndBlockIndex(int channelUid, unsigned int broadcastUid, int eventOffset, int& newChannelIndex, int& newBlockIndex) const;

    void FreeChannelMemory(int keepStart, int keepEnd);
    bool FreeProgrammeMemory(int firstChannel, int lastChannel, int firstBlock, int lastBlock);
    void FreeRulerMemory(int keepStart, int keepEnd);

    std::shared_ptr<CFileItem> GetChannelItem(int iIndex) const { return m_channelItems[iIndex]; }
    bool HasChannelItems() const { return !m_channelItems.empty(); }
    int ChannelItemsSize() const { return static_cast<int>(m_channelItems.size()); }
    int GetLastChannel() const
    {
      return m_channelItems.empty() ? -1 : static_cast<int>(m_channelItems.size()) - 1;
    }

    std::shared_ptr<CFileItem> GetRulerItem(int iIndex) const { return m_rulerItems[iIndex]; }
    int RulerItemsSize() const { return static_cast<int>(m_rulerItems.size()); }

    int GridItemsSize() const { return m_blocks; }
    bool IsSameGridItem(int iChannel, int iBlock1, int iBlock2) const;
    std::shared_ptr<CFileItem> GetGridItem(int iChannel, int iBlock) const;
    int GetGridItemStartBlock(int iChannel, int iBlock) const;
    int GetGridItemEndBlock(int iChannel, int iBlock) const;
    CDateTime GetGridItemEndTime(int iChannel, int iBlock) const;
    float GetGridItemWidth(int iChannel, int iBlock) const;
    float GetGridItemOriginWidth(int iChannel, int iBlock) const;
    void DecreaseGridItemWidth(int iChannel, int iBlock, float fSize);

    bool IsZeroGridDuration() const { return (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0); }
    const CDateTime& GetGridStart() const { return m_gridStart; }
    const CDateTime& GetGridEnd() const { return m_gridEnd; }
    unsigned int GetGridStartPadding() const;

    unsigned int GetPageNowOffset() const;
    int GetNowBlock() const;
    int GetLastBlock() const { return m_blocks - 1; }

    CDateTime GetStartTimeForBlock(int block) const;
    int GetBlock(const CDateTime& datetime) const;
    int GetFirstEventBlock(const std::shared_ptr<CPVREpgInfoTag>& event) const;
    int GetLastEventBlock(const std::shared_ptr<CPVREpgInfoTag>& event) const;
    bool IsEventMemberOfBlock(const std::shared_ptr<CPVREpgInfoTag>& event, int iBlock) const;

    std::unique_ptr<CFileItemList> GetCurrentTimeLineItems() const;

  private:
    GridItem* GetGridItemPtr(int iChannel, int iBlock) const;
    std::shared_ptr<CFileItem> CreateGapItem(int iChannel) const;
    std::shared_ptr<CFileItem> GetItem(int iChannel, int iBlock) const;

    std::vector<std::shared_ptr<CPVREpgInfoTag>> GetEPGTimeline(
        int iChannel, const CDateTime& minEventEnd, const CDateTime& maxEventStart) const;

    struct EpgTags
    {
      std::vector<std::shared_ptr<CFileItem>> tags;
      int firstBlock = -1;
      int lastBlock = -1;
    };

    using EpgTagsMap = std::unordered_map<int, EpgTags>;

    std::shared_ptr<CFileItem> CreateEpgTags(int iChannel, int iBlock) const;
    std::shared_ptr<CFileItem> GetEpgTags(EpgTagsMap::iterator& itEpg,
                                          int iChannel,
                                          int iBlock) const;
    std::shared_ptr<CFileItem> GetEpgTagsBefore(EpgTags& epgTags, int iChannel, int iBlock) const;
    std::shared_ptr<CFileItem> GetEpgTagsAfter(EpgTags& epgTags, int iChannel, int iBlock) const;

    mutable EpgTagsMap m_epgItems;

    CDateTime m_gridStart;
    CDateTime m_gridEnd;

    std::vector<std::shared_ptr<CFileItem>> m_channelItems;
    std::vector<std::shared_ptr<CFileItem>> m_rulerItems;

    struct GridCoordinates
    {
      GridCoordinates(int _channel, int _block) : channel(_channel), block(_block) {}

      bool operator==(const GridCoordinates& other) const
      {
        return (channel == other.channel && block == other.block);
      }

      int channel = 0;
      int block = 0;
    };

    struct GridCoordinatesHash
    {
      std::size_t operator()(const GridCoordinates& coordinates) const
      {
        return std::hash<int>()(coordinates.channel) ^ std::hash<int>()(coordinates.block);
      }
    };

    mutable std::unordered_map<GridCoordinates, GridItem, GridCoordinatesHash> m_gridIndex;

    int m_blocks = 0;
    float m_fBlockSize = 0.0f;

    int m_firstActiveChannel = 0;
    int m_lastActiveChannel = 0;
    int m_firstActiveBlock = 0;
    int m_lastActiveBlock = 0;
  };
}
