#include "ansicolor.h"

#include <string>

/**
 * @file
 * @brief ANSIColor
 */

/**
 * Construct a new ANSIColor::ANSIColor object
 * with sensible defaults (White on Black).
 *
 */
ANSIColor::ANSIColor()
    : fg(COLOR::WHITE),
      bg(COLOR::BLACK),
      reset(0),
      bold(0),
      blink(0),
      inverse(0) {}

/**
 * Construct a new ANSIColor::ANSIColor object
 * with attribute set.
 *
 * @param[in] a ATTR
 */
ANSIColor::ANSIColor(ATTR a) : ANSIColor() { Attr(a); }

/* // This won't work.  There's no clear way to set bold colors easily.
ANSIColor::ANSIColor(int c1) {}

ANSIColor::ANSIColor(int c1, int c2) {}
*/

/**
 * Construct a new ANSIColor::ANSIColor object
 * with a foreground color.
 *
 * @param[in] f COLOR
 */
ANSIColor::ANSIColor(COLOR f) : ANSIColor() { fg = f; }

/**
 * Construct a new ANSIColor::ANSIColor object
 * with a foreground color and attribute.
 *
 * @param[in] f COLOR
 * @param[in] a ATTR
 */
ANSIColor::ANSIColor(COLOR f, ATTR a) : ANSIColor() {
  fg = f;
  Attr(a);
}

/**
 * Construct a new ANSIColor::ANSIColor object
 * with a foreground color and attributes.
 *
 * @param[in] f COLOR
 * @param[in] a1 ATTR
 * @param[in] a2 ATTR
 */
ANSIColor::ANSIColor(COLOR f, ATTR a1, ATTR a2) : ANSIColor() {
  fg = f;
  Attr(a1);
  Attr(a2);
}

ANSIColor::ANSIColor(std::initializer_list<int> il) : ANSIColor() {
  for (auto const &i : il) {
    switch (i) {
      case 1:
        bold = 1;
        break;
      case 5:
        blink = 1;
        break;
      case 30:
      case 31:
      case 32:
      case 33:
      case 34:
      case 35:
      case 36:
      case 37:
        fg = (COLOR)(i - 30);
        break;
      case 40:
      case 41:
      case 42:
      case 43:
      case 44:
      case 45:
      case 46:
      case 47:
        bg = (COLOR)(i - 40);
        break;
    }
  }
}

/**
 * Construct a new ANSIColor::ANSIColor object
 * with a foreground and background color.
 *
 * @param[in] f foreground COLOR
 * @param[in] b background COLOR
 */
ANSIColor::ANSIColor(COLOR f, COLOR b) : ANSIColor() {
  fg = f;
  bg = b;
}

/**
 * Construct a new ANSIColor::ANSIColor object
 * with a foreground color, background color,
 * and attribute.
 *
 * @param[in] f foreground COLOR
 * @param[in] b background COLOR
 * @param[in] a ATTR
 */
ANSIColor::ANSIColor(COLOR f, COLOR b, ATTR a) : ANSIColor() {
  fg = f;
  bg = b;
  Attr(a);
}

/**
 * Construct a new ANSIColor::ANSIColor object
 * with foreground, background color and attributes.
 *
 * @param[in] f foreground COLOR
 * @param[in] b background COLOR
 * @param[in] a1 ATTR
 * @param[in] a2 ATTR
 */
ANSIColor::ANSIColor(COLOR f, COLOR b, ATTR a1, ATTR a2) : ANSIColor() {
  fg = f;
  bg = b;
  Attr(a1);
  Attr(a2);
}

/**
 * Set attribute.  We return the object so
 * calls can be chained.
 *
 * @param[in] a ATTR
 * @return ANSIColor&
 */
ANSIColor &ANSIColor::Attr(ATTR a) {
  switch (a) {
    case ATTR::RESET:
      reset = 1;
      break;
    case ATTR::BOLD:
      bold = 1;
      break;
    case ATTR::BLINK:
      blink = 1;
      break;
    case ATTR::INVERSE:
      inverse = 1;
      break;
  }
  return *this;
}

/**
 * Equal operator.
 *
 * This compares colors and attributes, but ignores reset.
 *
 * @param[in] c const ANSIColor &
 * @return bool
 */
bool ANSIColor::operator==(const ANSIColor &c) const {
  return ((fg == c.fg) and (bg == c.bg) and (bold == c.bold) and
          (blink == c.blink) and (inverse == c.inverse));
}

/**
 * Not-equal operator.
 *
 * This compares colors and attributes, but ignores reset.
 *
 * @param[in] c const ANSIColor &
 * @return bool
 */
bool ANSIColor::operator!=(const ANSIColor &c) const {
  return !((fg == c.fg) and (bg == c.bg) and (bold == c.bold) and
           (blink == c.blink) and (inverse == c.inverse));
}

/**
 * @brief Set foreground color
 *
 * @param[in] f foreground COLOR
 */
void ANSIColor::setFg(COLOR f) {
  fg = f;
  reset = 0;
  bold = 0;
  blink = 0;
  inverse = 0;
}

/**
 * @brief Set foreground color and attribute
 *
 * @param[in] f foreground COLOR
 * @param[in] a ATTR
 */
void ANSIColor::setFg(COLOR f, ATTR a) {
  fg = f;
  attr(a);
}

/**
 * @brief Set background color
 *
 * @param[in] b background COLOR
 */
void ANSIColor::setBg(COLOR b) { bg = b; }

/**
 * @brief Set attribute
 *
 * This clears all the attributes before setting the selected ATTR.
 *
 * @param[in] a ATTR
 */
void ANSIColor::attr(ATTR a) {
  // first, clear all attributes
  reset = 0;
  bold = 0;
  blink = 0;
  inverse = 0;
  Attr(a);
}

/**
 * Output the full ANSI codes for attributes and color.
 * This does not look at the previous values.
 */
std::string ANSIColor::output(void) const {
  std::string clr(CSI);

  // check for special cases
  if (reset and (fg == COLOR::BLACK) and (bg == COLOR::BLACK)) {
    clr += "0m";
    return clr;
  }

  if (reset and (fg == COLOR::WHITE) and (bg == COLOR::BLACK)) {
    clr += "0m";
    return clr;
  }

  if (reset) {
    clr += "0;";
  }

  if (bold) {
    if (blink) {
      clr += "5;";
    }
    clr += "1;";
  } else {
    if (!reset) clr += "0;";
    if (blink) {
      clr += "5;";
    }
  }

  clr += std::to_string(30 + (int)fg) + ";";
  clr += std::to_string(40 + (int)bg) + "m";

  return clr;
}

std::string ANSIColor::operator()(void) const {
  std::string clr(CSI);

  // check for special cases
  if (reset and (fg == COLOR::BLACK) and (bg == COLOR::BLACK)) {
    clr += "0m";
    return clr;
  }

  if (reset and (fg == COLOR::WHITE) and (bg == COLOR::BLACK)) {
    clr += "0m";
    return clr;
  }

  if (reset) {
    clr += "0;";
  }

  if (bold) {
    if (blink) {
      clr += "5;";
    }
    clr += "1;";
  } else {
    if (!reset) clr += "0;";
    if (blink) {
      clr += "5;";
    }
  }

  clr += std::to_string(30 + (int)fg) + ";";
  clr += std::to_string(40 + (int)bg) + "m";

  return clr;
}

ANSIColor reset(ATTR::RESET);