// RootTheme.cc
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include "RootTheme.hh"

#include "defaults.hh"
#include "FbRootWindow.hh"
#include "FbCommands.hh"
#include "Screen.hh"

#include "FbTk/App.hh"
#include "FbTk/Font.hh"
#include "FbTk/Image.hh"
#include "FbTk/ImageControl.hh"
#include "FbTk/Resource.hh"
#include "FbTk/FileUtil.hh"
#include "FbTk/StringUtil.hh"
#include "FbTk/TextureRender.hh"
#include "FbTk/I18n.hh"

#include <X11/Xatom.h>
#include <iostream>

#include <sys/types.h>
#include <sys/wait.h>
#ifdef HAVE_CSTRING
  #include <cstring>
#else
  #include <string.h>
#endif

using std::string;

class BackgroundItem: public FbTk::ThemeItem<FbTk::Texture> {
public:
    BackgroundItem(FbTk::Theme &tm, const std::string &name, const std::string &altname):
        FbTk::ThemeItem<FbTk::Texture>(tm, name, altname),
        m_changed(false), m_loaded(false) {

    }

    void load(const std::string *o_name = 0, const std::string *o_altname = 0) {
        const string &m_name = (o_name == 0) ? name() : *o_name;
        const string &m_altname = (o_altname == 0) ? altName() : *o_altname;

        // if we got this far, then the background was loaded
        m_loaded = true;

        // create subnames
        string color_name(FbTk::ThemeManager::instance().
                          resourceValue(m_name + ".color", m_altname + ".Color"));
        string colorto_name(FbTk::ThemeManager::instance().
                            resourceValue(m_name + ".colorTo", m_altname + ".ColorTo"));
        string pixmap_name(FbTk::ThemeManager::instance().
                           resourceValue(m_name + ".pixmap", m_altname + ".Pixmap"));
        string mod_x(FbTk::ThemeManager::instance().
                     resourceValue(m_name + ".modX", m_altname + ".ModX"));
        string mod_y(FbTk::ThemeManager::instance().
                     resourceValue(m_name + ".modY", m_altname + ".ModY"));

        // validate mod_x and mod_y
        if (mod_x.length() > 2)
            mod_x.erase(2,mod_x.length()); // shouldn't be longer than 2 digits
        if (mod_y.length() > 2)
            mod_y.erase(2,mod_y.length()); // ditto
        // should be integers
        if (!mod_x.length() || mod_x[0] < '0' || mod_x[0] > '9' ||
            (mod_x.length() == 2 && (mod_x[1] < '0' || mod_x[1] > '9')))
            mod_x = "1";
        if (!mod_y.length() || mod_y[0] < '0' || mod_y[0] > '9' ||
            (mod_y.length() == 2 && (mod_y[1] < '0' || mod_y[1] > '9')))
            mod_y = "1";

        // remove whitespace from filename
        FbTk::StringUtil::removeFirstWhitespace(pixmap_name);
        FbTk::StringUtil::removeTrailingWhitespace(pixmap_name);

        // check if the background has been changed
        if (mod_x != m_mod_x || mod_y != m_mod_y || pixmap_name != m_filename ||
                color_name != m_color || colorto_name != m_color_to) {
            m_changed = true;
            m_mod_x = mod_x;
            m_mod_y = mod_y;
            m_filename = pixmap_name;

            // these aren't quite right because of defaults set below
            m_color = color_name;
            m_color_to = colorto_name;
        }

        // set default value if we failed to load colors
        if (!(*this)->color().setFromString(color_name.c_str(),
                                            theme().screenNum()))
            (*this)->color().setFromString("darkgray", theme().screenNum());

        if (!(*this)->colorTo().setFromString(colorto_name.c_str(),
                                              theme().screenNum()))
            (*this)->colorTo().setFromString("white", theme().screenNum());


        if (((*this)->type() & FbTk::Texture::SOLID) != 0 && ((*this)->type() & FbTk::Texture::FLAT) == 0)
            (*this)->calcHiLoColors(theme().screenNum());

        // we dont load any pixmap, using external command to set background pixmap
        (*this)->pixmap() = 0;
    }

    void setFromString(const char *str) {
        m_options = str; // save option string
        FbTk::ThemeItem<FbTk::Texture>::setFromString(str);
    }
    const std::string &filename() const { return m_filename; }
    const std::string &options() const { return m_options; }
    const std::string &colorString() const { return m_color; }
    const std::string &colorToString() const { return m_color_to; }
    const std::string &modX() const { return m_mod_x; }
    const std::string &modY() const { return m_mod_y; }
    bool changed() const { return m_changed; }
    bool loaded() const { return m_loaded; }
    void setApplied() { m_changed = false; }
    void unsetLoaded() { m_loaded = false; }
private:
    std::string m_filename, m_options;
    std::string m_color, m_color_to;
    std::string m_mod_x, m_mod_y;
    bool m_changed, m_loaded;
};


RootTheme::RootTheme(FbTk::ImageControl &image_control):
    FbTk::Theme(image_control.screenNumber()),
    m_background(new BackgroundItem(*this, "background", "Background")),
    m_opgc(RootWindow(FbTk::App::instance()->display(), image_control.screenNumber())),
    m_image_ctrl(image_control),
    m_first(true) {

    Display *disp = FbTk::App::instance()->display();
    m_opgc.setForeground(WhitePixel(disp, screenNum())^BlackPixel(disp, screenNum()));
    m_opgc.setFunction(GXxor);
    m_opgc.setSubwindowMode(IncludeInferiors);
    FbTk::ThemeManager::instance().loadTheme(*this);
}

RootTheme::~RootTheme() {
    delete m_background;
}

bool RootTheme::fallback(FbTk::ThemeItem_base &item) {
    // if background theme item was not found in the
    // style then mark background as not loaded so
    // we can deal with it in reconfigureTheme()
    if (item.name() == "background") {
        // mark no background loaded
        m_background->unsetLoaded();
        return true;
    }
    return false;
}

void RootTheme::reconfigTheme() {
    if (!m_background->loaded())
        return;

    if (!m_first && !m_background->changed())
        return;

    //
    // Else parse background from style
    //

    m_background->setApplied();

    // handle background option in style
    std::string filename = m_background->filename();
    FbTk::StringUtil::removeTrailingWhitespace(filename);
    FbTk::StringUtil::removeFirstWhitespace(filename);

    // if background argument is a file then
    // parse image options and call image setting
    // command specified in the resources
    std::string img_path = FbTk::Image::locateFile(filename);
    filename = FbTk::StringUtil::expandFilename(filename);
    std::string cmd = realProgramName("fbsetbg") + (m_first ? " -z " : " -Z ");

    // user explicitly requests NO background be set at all
    if (strstr(m_background->options().c_str(), "unset") != 0) {
        return;
    }
    // style doesn't wish to change the background
    if (strstr(m_background->options().c_str(), "none") != 0) {
        if (!m_first)
            return;
    } else if (!img_path.empty()) {
        // parse options
        if (strstr(m_background->options().c_str(), "tiled") != 0)
            cmd += "-t ";
        else if (strstr(m_background->options().c_str(), "centered") != 0)
            cmd += "-c ";
        else if (strstr(m_background->options().c_str(), "aspect") != 0)
            cmd += "-a ";
        else
            cmd += "-f ";

        cmd += img_path;
    } else if (FbTk::FileUtil::isDirectory(filename.c_str()) &&
               strstr(m_background->options().c_str(), "random") != 0) {
        cmd += "-r " + filename;
    } else {
        // render normal texture with fbsetroot
        cmd += "-b ";

        // Make sure the color strings are valid,
        // so we dont pass any `commands` that can be executed
        bool color_valid =
            FbTk::Color::validColorString(m_background->colorString().c_str(),
                                          screenNum());
        bool color_to_valid =
            FbTk::Color::validColorString(m_background->colorToString().c_str(),
                                          screenNum());

        std::string options;
        if (color_valid)
            cmd += "-foreground '" + m_background->colorString() + "' ";
        if (color_to_valid)
            cmd += "-background '" + m_background->colorToString() + "' ";

        if (strstr(m_background->options().c_str(), "mod") != 0)
            cmd += "-mod " + m_background->modX() + " " + m_background->modY();
        else if ((*m_background)->type() & FbTk::Texture::SOLID && color_valid)
            cmd += "-solid '" + m_background->colorString() + "' ";
        else if ((*m_background)->type() & FbTk::Texture::GRADIENT) {
            // remove whitespace from the options, since fbsetroot doesn't care
            // and dealing with sh and fbsetbg is impossible if we don't
            std::string options = m_background->options();
            options = FbTk::StringUtil::replaceString(options, " ", "");
            options = FbTk::StringUtil::replaceString(options, "\t", "");
            cmd += "-gradient " + options;
        }
    }

    // call command with options
    FbCommands::ExecuteCmd exec(cmd, screenNum());
    m_first = false;
    exec.execute();
}