#ifdef    HAVE_CONFIG_H
#include "../config.h"
#endif // HAVE_CONFIG_H

#include <assert.h>
#include "display.hh"

#include "util.hh"
#include "style.hh"

namespace otk {

Style::Style() : font(NULL)
{
}

Style::Style(unsigned int screen)
  : font(0), screen_number(screen)
{
}

Style::Style(unsigned int screen, BImageControl *ctrl)
  : image_control(ctrl), font(0), screen_number(screen)
{
}

Style::~Style() {
  if (font)
    delete font;

  if (close_button.mask != None)
    XFreePixmap(OBDisplay::display, close_button.mask);
  if (max_button.mask != None)
    XFreePixmap(OBDisplay::display, max_button.mask);
  if (icon_button.mask != None)
    XFreePixmap(OBDisplay::display, icon_button.mask);
  if (stick_button.mask != None)
    XFreePixmap(OBDisplay::display, stick_button.mask);

  max_button.mask = None;
  close_button.mask = None;
  icon_button.mask = None;
  stick_button.mask = None;
}

void Style::load(Configuration &style) {
  std::string s;

  // load fonts/fontsets
  if (font)
    delete font;

  font = readDatabaseFont("window.", style);

  // load window config
  t_focus = readDatabaseTexture("window.title.focus", "white", style);
  t_unfocus = readDatabaseTexture("window.title.unfocus", "black", style);

  l_focus = readDatabaseTexture("window.label.focus", "white", style);
  l_unfocus = readDatabaseTexture("window.label.unfocus", "black", style);

  h_focus = readDatabaseTexture("window.handle.focus", "white", style);
  h_unfocus = readDatabaseTexture("window.handle.unfocus", "black", style);

  g_focus = readDatabaseTexture("window.grip.focus", "white", style);
  g_unfocus = readDatabaseTexture("window.grip.unfocus", "black", style);

  b_focus = readDatabaseTexture("window.button.focus", "white", style);
  b_unfocus = readDatabaseTexture("window.button.unfocus", "black", style);
  b_pressed = readDatabaseTexture("window.button.pressed", "black", style);

  //if neither of these can be found, we will use the previous resource
  b_pressed_focus = readDatabaseTexture("window.button.pressed.focus",
                                        "black", style, true);
  b_pressed_unfocus = readDatabaseTexture("window.button.pressed.unfocus",
                                          "black", style, true);

  if (close_button.mask != None)
    XFreePixmap(OBDisplay::display, close_button.mask);
  if (max_button.mask != None)
    XFreePixmap(OBDisplay::display, max_button.mask);
  if (icon_button.mask != None)
    XFreePixmap(OBDisplay::display, icon_button.mask);
  if (stick_button.mask != None)
    XFreePixmap(OBDisplay::display, stick_button.mask);

  close_button.mask = max_button.mask = icon_button.mask
                    = icon_button.mask = None;
  
  readDatabaseMask("window.button.close.mask", close_button, style);
  readDatabaseMask("window.button.max.mask", max_button, style);
  readDatabaseMask("window.button.icon.mask", icon_button, style);
  readDatabaseMask("window.button.stick.mask", stick_button, style);

  // we create the window.frame texture by hand because it exists only to
  // make the code cleaner and is not actually used for display
  BColor color = readDatabaseColor("window.frame.focusColor", "white",
                                        style);
  f_focus = BTexture("solid flat", screen_number, image_control);
  f_focus.setColor(color);

  color = readDatabaseColor("window.frame.unfocusColor", "white", style);
  f_unfocus = BTexture("solid flat", screen_number, image_control);
  f_unfocus.setColor(color);

  l_text_focus = readDatabaseColor("window.label.focus.textColor",
                                   "black", style);
  l_text_unfocus = readDatabaseColor("window.label.unfocus.textColor",
                                     "white", style);

  b_pic_focus = readDatabaseColor("window.button.focus.picColor",
                                  "black", style);
  b_pic_unfocus = readDatabaseColor("window.button.unfocus.picColor",
                                    "white", style);

  justify = LeftJustify;

  if (style.getValue("window.justify", s)) {
    if (s == "right" || s == "Right")
      justify = RightJustify;
    else if (s == "center" || s == "Center")
      justify = CenterJustify;
  }

  // sanity checks
  if (t_focus.texture() == BTexture::Parent_Relative)
    t_focus = f_focus;
  if (t_unfocus.texture() == BTexture::Parent_Relative)
    t_unfocus = f_unfocus;
  if (h_focus.texture() == BTexture::Parent_Relative)
    h_focus = f_focus;
  if (h_unfocus.texture() == BTexture::Parent_Relative)
    h_unfocus = f_unfocus;

  border_color = readDatabaseColor("borderColor", "black", style);

  // load bevel, border and handle widths

  const ScreenInfo *s_info = OBDisplay::screenInfo(screen_number);
  unsigned int width = s_info->getRect().width();

  if (! style.getValue("handleWidth", handle_width) ||
      handle_width > width/2 || handle_width == 0)
    handle_width = 6;

  if (! style.getValue("borderWidth", border_width))
    border_width = 1;

  if (! style.getValue("bevelWidth", bevel_width)
      || bevel_width > width/2 || bevel_width == 0)
    bevel_width = 3;

  if (! style.getValue("frameWidth", frame_width)
      || frame_width > width/2)
    frame_width = bevel_width;

  if (style.getValue("rootCommand", s))
    bexec(s, s_info->displayString());
}


void Style::doJustify(const std::string &text, int &start_pos,
                      unsigned int max_length,
                      unsigned int modifier) const {
  size_t text_len = text.size();
  unsigned int length;

  do {
    length = font->measureString(std::string(text, 0, text_len)) + modifier;
  } while (length > max_length && text_len-- > 0);

  switch (justify) {
  case RightJustify:
    start_pos += max_length - length;
    break;

  case CenterJustify:
    start_pos += (max_length - length) / 2;
    break;

  case LeftJustify:
  default:
    break;
  }
}


void Style::readDatabaseMask(const std::string &rname, PixmapMask &pixmapMask,
                             const Configuration &style) {
  Window root_window = OBDisplay::screenInfo(screen_number)->getRootWindow();
  std::string s;
  int hx, hy; //ignored
  int ret = BitmapOpenFailed; //default to failure.
  
  if (style.getValue(rname, s))
  {
    if (s[0] != '/' && s[0] != '~')
    {
      std::string xbmFile = std::string("~/.openbox/buttons/") + s;
      ret = XReadBitmapFile(OBDisplay::display, root_window,
                            expandTilde(xbmFile).c_str(), &pixmapMask.w,
                            &pixmapMask.h, &pixmapMask.mask, &hx, &hy);
    } else
      ret = XReadBitmapFile(OBDisplay::display, root_window,
                            expandTilde(s).c_str(), &pixmapMask.w,
                            &pixmapMask.h, &pixmapMask.mask, &hx, &hy);
    
    if (ret == BitmapSuccess)
      return;
  }

  pixmapMask.mask = None;
  pixmapMask.w = pixmapMask.h = 0;
}


BTexture Style::readDatabaseTexture(const std::string &rname,
                                         const std::string &default_color,
                                         const Configuration &style, 
                                         bool allowNoTexture)
{
  BTexture texture;
  std::string s;

  if (style.getValue(rname, s))
    texture = BTexture(s);
  else if (allowNoTexture) //no default
    texture.setTexture(BTexture::NoTexture);
  else
    texture.setTexture(BTexture::Solid | BTexture::Flat);

  // associate this texture with this screen
  texture.setScreen(screen_number);
  texture.setImageControl(image_control);

  if (texture.texture() != BTexture::NoTexture) {
    texture.setColor(readDatabaseColor(rname + ".color", default_color,
                                       style));
    texture.setColorTo(readDatabaseColor(rname + ".colorTo", default_color,
                                         style));
    texture.setBorderColor(readDatabaseColor(rname + ".borderColor",
                                             default_color, style));
  }

  return texture;
}


BColor Style::readDatabaseColor(const std::string &rname,
                                     const std::string &default_color,
                                     const Configuration &style) {
  BColor color;
  std::string s;
  if (style.getValue(rname, s))
    color = BColor(s, screen_number);
  else
    color = BColor(default_color, screen_number);
  return color;
}


BFont *Style::readDatabaseFont(const std::string &rbasename,
                                    const Configuration &style) {
  std::string fontname;

  std::string s;

  int i;
  if (style.getValue(rbasename + "xft.font", s) &&
      style.getValue(rbasename + "xft.size", i)) {
    std::string family = s;
    bool bold = False;
    bool italic = False;
    bool dropShadow = False;

    if (style.getValue(rbasename + "xft.flags", s)) {
      if (s.find("bold") != std::string::npos)
        bold = True;
      if (s.find("italic") != std::string::npos)
        italic = True;
      if (s.find("shadow") != std::string::npos)
        dropShadow = True;
    }
    
    unsigned char offset = 1;
    if (style.getValue(rbasename + "xft.shadow.offset", s)) {
      offset = atoi(s.c_str()); //doesn't detect errors
      if (offset > CHAR_MAX)
        offset = 1;
    }

    unsigned char tint = 0x40;
    if (style.getValue(rbasename + "xft.shadow.tint", s)) {
      tint = atoi(s.c_str());
    }

    
    BFont *b = new BFont(screen_number, family, i, bold, italic,
                         dropShadow && shadow_fonts,
                         offset, tint, aa_fonts);
    if (b->valid())
      return b;
    delete b;
  }
    
  exit(2);  // can't continue without a font
}

}