/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CellData_h__
#define CellData_h__

#include "nsISupports.h"
#include "nsITableCellLayout.h"  // for MAX_COLSPAN / MAX_ROWSPAN
#include "nsCoord.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/WritingModes.h"
#include <algorithm>
#include <stdint.h>

class nsTableCellFrame;
class nsCellMap;
class BCCellData;

/**
 * Data stored by nsCellMap to rationalize rowspan and colspan cells.
 */
class CellData {
 public:
  /** Initialize the mOrigCell pointer
   * @param aOrigCell  the table cell frame which will be stored in mOrigCell.
   */
  void Init(nsTableCellFrame* aCellFrame);

  /** does a cell originate from here
   * @return    is true if a cell corresponds to this cellmap entry
   */
  bool IsOrig() const;

  /** is the celldata valid
   * @return    is true if no cell originates and the cell is not spanned by
   *            a row- or colspan. mBits are 0 in this case and mOrigCell is
   *            nullptr
   */
  bool IsDead() const;

  /** is the entry spanned by row- or a colspan
   * @return    is true if the entry is spanned by a row- or colspan
   */
  bool IsSpan() const;

  /** is the entry spanned by rowspan
   * @return    is true if the entry is spanned by a rowspan
   */
  bool IsRowSpan() const;

  /** is the entry spanned by a zero rowspan
   * zero rowspans span all cells starting from the originating cell down to
   * the end of the rowgroup or a cell originating in the same column
   * @return    is true if the entry is spanned by a zero rowspan
   */
  bool IsZeroRowSpan() const;

  /** mark the current entry as spanned by a zero rowspan
   * @param aIsZero    if true mark the entry as covered by a zero rowspan
   */
  void SetZeroRowSpan(bool aIsZero);

  /** get the distance from the current entry to the corresponding origin of the
   * rowspan
   * @return    containing the distance in the column to the originating cell
   */
  uint32_t GetRowSpanOffset() const;

  /** set the distance from the current entry to the corresponding origin of
   * the rowspan
   * @param    the distance in the column to the originating cell
   */
  void SetRowSpanOffset(uint32_t aSpan);

  /** is the entry spanned by colspan
   * @return    is true if the entry is spanned by a colspan
   */
  bool IsColSpan() const;

  /** get the distance from the current entry to the corresponding origin of
   *  the colspan
   * @return    containing the distance in the row to the originating cell
   */
  uint32_t GetColSpanOffset() const;

  /** set the distance from the current entry to the corresponding origin of the
   * colspan
   * @param    the distance in the column to the originating cell
   */
  void SetColSpanOffset(uint32_t aSpan);

  /** is the entry spanned by a row- and a colspan
   * @return    is true if the entry is spanned by a row- and a colspan
   */
  bool IsOverlap() const;

  /** mark the current entry as spanned by a row- and a colspan
   * @param aOverlap    if true mark the entry as covered by a row- and
   *                    a colspan
   */
  void SetOverlap(bool aOverlap);

  /** get the table cell frame for this entry
   * @return    a pointer to the cellframe, this will be nullptr when the entry
   *            is only a spanned entry
   */
  nsTableCellFrame* GetCellFrame() const;

 private:
  friend class nsCellMap;
  friend class BCCellData;

  /**
   * Implemented in nsCellMap.cpp
   *
   * @param aOrigCell  the table cell frame which will be stored in mOrigCell.
   */
  explicit CellData(nsTableCellFrame* aOrigCell);

  ~CellData();  // implemented in nsCellMap.cpp

 protected:
  // this union relies on the assumption that an object (not primitive type)
  // does not start on an odd bit boundary. If mSpan is 0 then mOrigCell is in
  // effect and the data does not represent a span. If mSpan is 1, then mBits is
  // in effect and the data represents a span. mBits must match the size of
  // mOrigCell on both 32- and 64-bit platforms.
  union {
    nsTableCellFrame* mOrigCell;
    uintptr_t mBits;
  };
};

// Border Collapsing Cell Data
enum BCBorderOwner {
  eTableOwner = 0,
  eColGroupOwner = 1,
  eAjaColGroupOwner = 2,  // col group to the left
  eColOwner = 3,
  eAjaColOwner = 4,  // col to the left
  eRowGroupOwner = 5,
  eAjaRowGroupOwner = 6,  // row group above
  eRowOwner = 7,
  eAjaRowOwner = 8,  // row above
  eCellOwner = 9,
  eAjaCellOwner = 10  // cell to the top or to the left
};

// These are the max sizes that are stored. If they are exceeded, then the max
// is stored and the actual value is computed when needed.
#define MAX_BORDER_WIDTH nscoord((1u << (sizeof(uint16_t) * 8)) - 1)

// The half of border on inline/block-axis start side
static inline nscoord BC_BORDER_START_HALF(nscoord aCoord) {
  return aCoord - aCoord / 2;
}
// The half of border on inline/block-axis end side
static inline nscoord BC_BORDER_END_HALF(nscoord aCoord) { return aCoord / 2; }

// BCData stores the bstart and istart border info and the corner connecting the
// two.
class BCData {
 public:
  BCData();

  ~BCData();

  nscoord GetIStartEdge(BCBorderOwner& aOwner, bool& aStart) const;

  void SetIStartEdge(BCBorderOwner aOwner, nscoord aSize, bool aStart);

  nscoord GetBStartEdge(BCBorderOwner& aOwner, bool& aStart) const;

  void SetBStartEdge(BCBorderOwner aOwner, nscoord aSize, bool aStart);

  nscoord GetCorner(mozilla::LogicalSide& aOwnerSide, bool& aBevel) const;

  void SetCorner(nscoord aSubSize, mozilla::LogicalSide aOwner, bool aBevel);

  inline bool IsIStartStart() const { return (bool)mIStartStart; }

  inline void SetIStartStart(bool aValue) { mIStartStart = aValue; }

  inline bool IsBStartStart() const { return (bool)mBStartStart; }

  inline void SetBStartStart(bool aValue) { mBStartStart = aValue; }

 protected:
  nscoord mIStartSize;        // size of iStart border
  nscoord mBStartSize;        // size of bStart border
  nscoord mCornerSubSize;     // size of the largest border not in the
                              //   dominant plane (for example, if corner is
                              //   owned by the segment to its bStart or bEnd,
                              //   then the size is the max of the border
                              //   sizes of the segments to its iStart or iEnd.
  unsigned mIStartOwner : 4;  // owner of iStart border
  unsigned mBStartOwner : 4;  // owner of bStart border
  unsigned mIStartStart : 1;  // set if this is the start of a block-dir border
                              // segment
  unsigned mBStartStart : 1;  // set if this is the start of an inline-dir
                              // border segment
  unsigned mCornerSide : 2;   // LogicalSide of the owner of the bStart-iStart
                              // corner relative to the corner
  unsigned mCornerBevel : 1;  // is the corner beveled (only two segments,
                              // perpendicular, not dashed or dotted).
};

// BCCellData entries replace CellData entries in the cell map if the border
// collapsing model is in effect. BCData for a row and col entry contains the
// left and top borders of cell at that row and col and the corner connecting
// the two. The right borders of the cells in the last col and the bottom
// borders of the last row are stored in separate BCData entries in the cell
// map.
class BCCellData : public CellData {
 public:
  explicit BCCellData(nsTableCellFrame* aOrigCell);
  ~BCCellData();

  BCData mData;
};

// The layout of a celldata is as follows.  The top 10 bits are the colspan
// offset (which is enough to represent our allowed values 1-1000 for colspan).
// Then there are two bits of flags.
// XXXmats Then one unused bit that we should decide how to use in bug 862624.
// Then 16 bits of rowspan offset (which
// lets us represent numbers up to 65535.  Then another 3 bits of flags.

// num bits to shift right to get right aligned col span
#define COL_SPAN_SHIFT 22
// num bits to shift right to get right aligned row span
#define ROW_SPAN_SHIFT 3

// the col offset to the data containing the original cell.
#define COL_SPAN_OFFSET (0x3FF << COL_SPAN_SHIFT)
// the row offset to the data containing the original cell
#define ROW_SPAN_OFFSET (0xFFFF << ROW_SPAN_SHIFT)

// And the flags
#define SPAN 0x00000001                       // there a row or col span
#define ROW_SPAN 0x00000002                   // there is a row span
#define ROW_SPAN_0 0x00000004                 // the row span is 0
#define COL_SPAN (1 << (COL_SPAN_SHIFT - 2))  // there is a col span
#define OVERLAP \
  (1 << (COL_SPAN_SHIFT - 1))  // there is a row span and
                               // col span but not by
                               // same cell

inline nsTableCellFrame* CellData::GetCellFrame() const {
  if (SPAN != (SPAN & mBits)) {
    return mOrigCell;
  }
  return nullptr;
}

inline void CellData::Init(nsTableCellFrame* aCellFrame) {
  mOrigCell = aCellFrame;
}

inline bool CellData::IsOrig() const {
  return ((nullptr != mOrigCell) && (SPAN != (SPAN & mBits)));
}

inline bool CellData::IsDead() const { return (0 == mBits); }

inline bool CellData::IsSpan() const { return (SPAN == (SPAN & mBits)); }

inline bool CellData::IsRowSpan() const {
  return (SPAN == (SPAN & mBits)) && (ROW_SPAN == (ROW_SPAN & mBits));
}

inline bool CellData::IsZeroRowSpan() const {
  return (SPAN == (SPAN & mBits)) && (ROW_SPAN == (ROW_SPAN & mBits)) &&
         (ROW_SPAN_0 == (ROW_SPAN_0 & mBits));
}

inline void CellData::SetZeroRowSpan(bool aIsZeroSpan) {
  if (SPAN == (SPAN & mBits)) {
    if (aIsZeroSpan) {
      mBits |= ROW_SPAN_0;
    } else {
      mBits &= ~ROW_SPAN_0;
    }
  }
}

inline uint32_t CellData::GetRowSpanOffset() const {
  if ((SPAN == (SPAN & mBits)) && ((ROW_SPAN == (ROW_SPAN & mBits)))) {
    return (uint32_t)((mBits & ROW_SPAN_OFFSET) >> ROW_SPAN_SHIFT);
  }
  return 0;
}

inline void CellData::SetRowSpanOffset(uint32_t aSpan) {
  MOZ_ASSERT(aSpan > 0, "a zero-sized span is nonsensical");
  MOZ_ASSERT(aSpan <= MAX_ROWSPAN, "span shouldn't exceed what we can handle");
  aSpan = std::min(aSpan, static_cast<uint32_t>(MAX_ROWSPAN));

  mBits &= ~ROW_SPAN_OFFSET;
  mBits |= (aSpan << ROW_SPAN_SHIFT);
  mBits |= SPAN;
  mBits |= ROW_SPAN;
}

inline bool CellData::IsColSpan() const {
  return (SPAN == (SPAN & mBits)) && (COL_SPAN == (COL_SPAN & mBits));
}

inline uint32_t CellData::GetColSpanOffset() const {
  if ((SPAN == (SPAN & mBits)) && ((COL_SPAN == (COL_SPAN & mBits)))) {
    return (uint32_t)((mBits & COL_SPAN_OFFSET) >> COL_SPAN_SHIFT);
  }
  return 0;
}

inline void CellData::SetColSpanOffset(uint32_t aSpan) {
  MOZ_ASSERT(aSpan > 0, "a zero-sized span is nonsensical");
  MOZ_ASSERT(aSpan <= MAX_COLSPAN, "span shouldn't exceed what we can handle");
  aSpan = std::min(aSpan, static_cast<uint32_t>(MAX_COLSPAN));

  mBits &= ~COL_SPAN_OFFSET;
  mBits |= (aSpan << COL_SPAN_SHIFT);

  mBits |= SPAN;
  mBits |= COL_SPAN;
}

inline bool CellData::IsOverlap() const {
  return (SPAN == (SPAN & mBits)) && (OVERLAP == (OVERLAP & mBits));
}

inline void CellData::SetOverlap(bool aOverlap) {
  if (SPAN == (SPAN & mBits)) {
    if (aOverlap) {
      mBits |= OVERLAP;
    } else {
      mBits &= ~OVERLAP;
    }
  }
}

inline BCData::BCData() {
  mIStartOwner = mBStartOwner = eCellOwner;
  SetBStartStart(true);
  SetIStartStart(true);
  mIStartSize = mCornerSubSize = mBStartSize = 0;
  mCornerSide = static_cast<uint8_t>(mozilla::LogicalSide::BStart);
  mCornerBevel = false;
}

inline BCData::~BCData() = default;

inline nscoord BCData::GetIStartEdge(BCBorderOwner& aOwner,
                                     bool& aStart) const {
  aOwner = (BCBorderOwner)mIStartOwner;
  aStart = IsIStartStart();

  return (nscoord)mIStartSize;
}

inline void BCData::SetIStartEdge(BCBorderOwner aOwner, nscoord aSize,
                                  bool aStart) {
  mIStartOwner = aOwner;
  mIStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize;
  SetIStartStart(aStart);
}

inline nscoord BCData::GetBStartEdge(BCBorderOwner& aOwner,
                                     bool& aStart) const {
  aOwner = (BCBorderOwner)mBStartOwner;
  aStart = IsBStartStart();

  return (nscoord)mBStartSize;
}

inline void BCData::SetBStartEdge(BCBorderOwner aOwner, nscoord aSize,
                                  bool aStart) {
  mBStartOwner = aOwner;
  mBStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize;
  SetBStartStart(aStart);
}

inline nscoord BCData::GetCorner(mozilla::LogicalSide& aOwnerSide,
                                 bool& aBevel) const {
  aOwnerSide = mozilla::LogicalSide(mCornerSide);
  aBevel = (bool)mCornerBevel;
  return mCornerSubSize;
}

inline void BCData::SetCorner(nscoord aSubSize, mozilla::LogicalSide aOwnerSide,
                              bool aBevel) {
  mCornerSubSize = aSubSize;
  mCornerSide = static_cast<uint8_t>(aOwnerSide);
  mCornerBevel = aBevel;
}

#endif
