/*
  Copyright(C) 2011-2016 Brazil

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#pragma once

#include "dat.hpp"

namespace grn {
namespace dat {

class GRN_DAT_API Check {
 public:
  Check() : value_(0) {}

  bool operator==(const Check &rhs) const {
    return value_ == rhs.value_;
  }

  // The most significant bit represents whether or not the node ID is used as
  // an offset. Note that the MSB is independent of the other bits.
  bool is_offset() const {
    return (value_ & IS_OFFSET_FLAG) == IS_OFFSET_FLAG;
  }

  UInt32 except_is_offset() const {
    GRN_DAT_DEBUG_THROW_IF(is_phantom());
    return value_ & ~IS_OFFSET_FLAG;
  }

  // A phantom node is a node that has never been used, and such a node is also
  // called an empty element. Phantom nodes form a doubly linked list in each
  // block, and the linked list is represented by next() and prev().
  bool is_phantom() const {
    return (value_ & IS_PHANTOM_FLAG) == IS_PHANTOM_FLAG;
  }

  UInt32 next() const {
    GRN_DAT_DEBUG_THROW_IF(!is_phantom());
    return (value_ >> NEXT_SHIFT) & BLOCK_MASK;
  }
  UInt32 prev() const {
    GRN_DAT_DEBUG_THROW_IF(!is_phantom());
    return (value_ >> PREV_SHIFT) & BLOCK_MASK;
  }

  // A label is attached to each non-phantom node. A label is represented by
  // a byte except for a terminal label '\256'. Note that a phantom node always
  // returns an invalid label with its phantom bit flag so as to reject invalid
  // transitions.
  UInt32 label() const {
    return value_ & (IS_PHANTOM_FLAG | LABEL_MASK);
  }

  // A non-phantom node has the labels of the first child and the next sibling.
  // Note that INVALID_LABEL is stored if the node has no child nodes or has
  // no more siblings.
  UInt32 child() const {
    return (value_ >> CHILD_SHIFT) & LABEL_MASK;
  }
  UInt32 sibling() const {
    return (value_ >> SIBLING_SHIFT) & LABEL_MASK;
  }

  void set_is_offset(bool x) {
    if (x) {
      GRN_DAT_DEBUG_THROW_IF(is_offset());
      value_ |= IS_OFFSET_FLAG;
    } else {
      GRN_DAT_DEBUG_THROW_IF(!is_offset());
      value_ &= ~IS_OFFSET_FLAG;
    }
  }

  void set_except_is_offset(UInt32 x) {
    GRN_DAT_DEBUG_THROW_IF(is_phantom());
    GRN_DAT_DEBUG_THROW_IF((x & IS_OFFSET_FLAG) == IS_OFFSET_FLAG);
    value_ = (value_ & IS_OFFSET_FLAG) | x;
  }

  // To reject a transition to an incomplete node, set_is_phantom() invalidates
  // its label and links when it becomes non-phantom.
  void set_is_phantom(bool x) {
    if (x) {
      GRN_DAT_DEBUG_THROW_IF(is_phantom());
      value_ |= IS_PHANTOM_FLAG;
    } else {
      GRN_DAT_DEBUG_THROW_IF(!is_phantom());
      value_ = (value_ & IS_OFFSET_FLAG) | (INVALID_LABEL << CHILD_SHIFT) |
          (INVALID_LABEL << SIBLING_SHIFT) | INVALID_LABEL;
    }
  }

  void set_next(UInt32 x) {
    GRN_DAT_DEBUG_THROW_IF(!is_phantom());
    GRN_DAT_DEBUG_THROW_IF(x > BLOCK_MASK);
    value_ = (value_ & ~(BLOCK_MASK << NEXT_SHIFT)) | (x << NEXT_SHIFT);
  }
  void set_prev(UInt32 x) {
    GRN_DAT_DEBUG_THROW_IF(!is_phantom());
    GRN_DAT_DEBUG_THROW_IF(x > BLOCK_MASK);
    value_ = (value_ & ~(BLOCK_MASK << PREV_SHIFT)) | (x << PREV_SHIFT);
  }

  void set_label(UInt32 x) {
    GRN_DAT_DEBUG_THROW_IF(is_phantom());
    GRN_DAT_DEBUG_THROW_IF(x > MAX_LABEL);
    value_ = (value_ & ~LABEL_MASK) | x;
  }

  void set_child(UInt32 x) {
    GRN_DAT_DEBUG_THROW_IF(is_phantom());
    GRN_DAT_DEBUG_THROW_IF(x > MAX_LABEL);
    value_ = (value_ & ~(LABEL_MASK << CHILD_SHIFT)) | (x << CHILD_SHIFT);
  }
  void set_sibling(UInt32 x) {
    GRN_DAT_DEBUG_THROW_IF(is_phantom());
    GRN_DAT_DEBUG_THROW_IF(label() > MAX_LABEL);
    GRN_DAT_DEBUG_THROW_IF((sibling() != INVALID_LABEL) && (x == INVALID_LABEL));
    value_ = (value_ & ~(LABEL_MASK << SIBLING_SHIFT)) | (x << SIBLING_SHIFT);
  }

 private:
  UInt32 value_;

  static const UInt32 IS_OFFSET_FLAG = 1U << 31;
  static const UInt32 IS_PHANTOM_FLAG = 1U << 30;
  static const UInt32 NEXT_SHIFT = 9;
  static const UInt32 PREV_SHIFT = 18;
  static const UInt32 CHILD_SHIFT = 9;
  static const UInt32 SIBLING_SHIFT = 18;
};

}  // namespace dat
}  // namespace grn
