// ImageControl.cc for FbTk - Fluxbox Toolkit
// Copyright (c) 2001 - 2006 Henrik Kinnunen (fluxbox at users.sourceforge.net)
//
// Image.cc for Blackbox - an X11 Window manager
// Copyright (c) 1997 - 2000 Brad Hughes (bhughes at tcac.net)
//
// 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 "ImageControl.hh"

#include "TextureRender.hh"
#include "Texture.hh"
#include "App.hh"
#include "SimpleCommand.hh"
#include "I18n.hh"

//use GNU extensions
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif // _GNU_SOURCE


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

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif // HAVE_SYS_TYPES_H

#ifdef HAVE_CSTDLIB
  #include <cstdlib>
#else
  #include <stdlib.h>
#endif
#ifdef HAVE_CSTDIO
  #include <cstdio>
#else
  #include <stdio.h>
#endif

#include <iostream>

using std::cerr;
using std::endl;
using std::list;

namespace FbTk {

namespace { // anonymous

#ifdef TIMEDCACHE
bool s_timed_cache = true;
#else
bool s_timed_cache = false;
#endif // TIMEDCACHE


void initColortables(unsigned char red[256], unsigned char green[256], unsigned char blue[256],
      int red_bits, int green_bits, int blue_bits) {

    for (unsigned int i = 0; i < 256; i++) {
        red[i] = i / red_bits;
        green[i] = i / green_bits;
        blue[i] = i / blue_bits;
    }
}

// tries to allocate all unallocated 'colors' by finding a close color based
// upon entries in the colormap.
//
void allocateUnallocatedColors(std::vector<XColor> colors, Display* dpy, Colormap cmap, int screen_depth) {

    unsigned int i;

    bool done = true;

    // first run, just try to allocate the colors
    for (i = 0; i < colors.size(); i++) {

        if (colors[i].flags == 0) {
            if (! XAllocColor(dpy, cmap, &colors[i])) {
                fprintf(stderr, "couldn't alloc color %i %i %i\n",
                        colors[i].red, colors[i].green, colors[i].blue);
                colors[i].flags = 0;
                done = false;
            } else
                colors[i].flags = DoRed|DoGreen|DoBlue;
        }
    }

    if (done)
        return;

    // 'icolors' will hold the first 'nr_icolors' colors of the
    // given (indexed) colormap.
    const size_t nr_icolors = std::min(256, 1 << screen_depth);
    std::vector<XColor> icolors(nr_icolors);

    // give each icolor an index
    for (i = 0; i < nr_icolors; i++)
        icolors[i].pixel = i;

    // query the colors of the colormap and store them into 'icolors'
    XQueryColors(dpy, cmap, &icolors[0], nr_icolors);

    // try to find a close color for all not allocated colors
    for (i = 0; i < colors.size(); i++) {

        if (colors[i].flags == 0) { // color is not allocated

            unsigned long chk = 0xffffffff;
            unsigned long close = 0;

            // iterate over the indexed colors 'icolors' and find
            // a close color. 
            //
            // 2 passes to improve the result of the first pass

            char pass = 2;
            while (pass--) {
                for (unsigned int ii = 0; ii < nr_icolors; ii++) {

                    int r = (colors[i].red - icolors[i].red) >> 8;
                    int g = (colors[i].green - icolors[i].green) >> 8;
                    int b = (colors[i].blue - icolors[i].blue) >> 8;
                    unsigned long pixel = (r * r) + (g * g) + (b * b);

                    if (pixel < chk) {
                        chk = pixel;
                        close = ii;
                    }

                    // store the indexed color
                    colors[i].red = icolors[close].red;
                    colors[i].green = icolors[close].green;
                    colors[i].blue = icolors[close].blue;

                    // try to allocate it
                    if (XAllocColor(dpy, cmap, &colors[i])) {
                        colors[i].flags = DoRed|DoGreen|DoBlue; // mark it allocated
                        break;
                    }
                }
            }
        }
    }
}


} // end anonymous namespace

struct ImageControl::Cache {
    Pixmap pixmap;
    Pixmap texture_pixmap;
    Orientation orient;
    unsigned int count, width, height;
    unsigned long pixel1, pixel2, texture;
};

ImageControl::ImageControl(int screen_num,
                           int cpc, unsigned long cache_timeout, unsigned long cmax):
    m_colors_per_channel(cpc),
    m_screen_num(screen_num) {

    Display *disp = FbTk::App::instance()->display();

    m_screen_depth = DefaultDepth(disp, screen_num);
    m_visual = DefaultVisual(disp, screen_num);
    m_colormap = DefaultColormap(disp, screen_num);

    cache_max = cmax;

    if (cache_timeout && s_timed_cache) {
        m_timer.setTimeout(cache_timeout * FbTk::FbTime::IN_MILLISECONDS);
        RefCount<Command<void> > clean_cache(new SimpleCommand<ImageControl>(*this, &ImageControl::cleanCache));
        m_timer.setCommand(clean_cache);
        m_timer.start();
    }

    createColorTable();
}


ImageControl::~ImageControl() {

    Display *disp = FbTk::App::instance()->display();

    if (!m_colors.empty()) {
        std::vector<unsigned long> pixels(m_colors.size());

        for (unsigned int i = 0; i < m_colors.size(); i++)
            pixels[i] = m_colors[i].pixel;

        XFreeColors(disp, m_colormap, &pixels[0], pixels.size(), 0);
    }

    if (!cache.empty()) {
        CacheList::iterator it = cache.begin();
        CacheList::iterator it_end = cache.end();
        for (; it != it_end; ++it) {
            XFreePixmap(disp, (*it)->pixmap);
            delete (*it);
        }
    }
}


Pixmap ImageControl::searchCache(unsigned int width, unsigned int height,
                                 const Texture &text, FbTk::Orientation orient) const {

    if (text.pixmap().drawable() != None) {
        // do comparsion with width/height and texture_pixmap
        CacheList::iterator it = cache.begin();
        CacheList::iterator it_end = cache.end();
        for (; it != it_end; ++it) {
            if ((*it)->texture_pixmap == text.pixmap().drawable() &&
                (*it)->orient == orient &&
                (*it)->width == width &&
                (*it)->height == height &&
                (*it)->texture == text.type()) {
                (*it)->count++;
                return (*it)->pixmap;
            }
        }
        return None;
    }

    /*    Cache tmp;
    tmp.texture_pixmap = text.pixmap().drawable();
    tmp.width = width;
    tmp.height = height;
    tmp.texture = text.type();
    tmp.pixel1 = text.color().pixel();
    tmp.pixel2 = text.colorTo().pixel();
    */

    CacheList::iterator it = cache.begin();
    CacheList::iterator it_end = cache.end();
    for (; it != it_end; ++it) {
        if (((*it)->width == width) &&
            ((*it)->orient == orient) &&
            ((*it)->height == height) &&
            ((*it)->texture == text.type()) &&
            ((*it)->pixel1 == text.color().pixel())) {
            if (text.type() & FbTk::Texture::GRADIENT) {
                if ((*it)->pixel2 == text.colorTo().pixel()) {
                    (*it)->count++;
                    return (*it)->pixmap;
                }
            } else {
                (*it)->count++;
                return (*it)->pixmap;
            }
        }
    }

    return None;

}


Pixmap ImageControl::renderImage(unsigned int width, unsigned int height,
                                 const FbTk::Texture &texture,
                                 FbTk::Orientation orient,
                                 bool use_cache ) {

    if (texture.type() & FbTk::Texture::PARENTRELATIVE)
        return ParentRelative;

    // If we are not suppose to cache this pixmap, just render and return it
    if ( ! use_cache) {
        TextureRender image(*this, width, height, orient);
        return image.render(texture);
    }

    // search cache first
    Pixmap pixmap = searchCache(width, height, texture, orient);
    if (pixmap) {
        return pixmap; // return cache item
    }

    // render new image

    TextureRender image(*this, width, height, orient);
    pixmap = image.render(texture);

    if (pixmap) {
        // create new cache item and add it to cache list

        Cache *tmp = new Cache;

        tmp->pixmap = pixmap;
        tmp->texture_pixmap = texture.pixmap().drawable();
        tmp->orient = orient;
        tmp->width = width;
        tmp->height = height;
        tmp->count = 1;
        tmp->texture = texture.type();
        tmp->pixel1 = texture.color().pixel();

        if (texture.type() & FbTk::Texture::GRADIENT)
            tmp->pixel2 = texture.colorTo().pixel();
        else
            tmp->pixel2 = 0l;

        cache.push_back(tmp);

        if (cache.size() > cache_max)
            cleanCache();

        return pixmap;
    }

    return None;
}


void ImageControl::removeImage(Pixmap pixmap) {
    if (!pixmap)
        return;

    CacheList::iterator it = cache.begin();
    CacheList::iterator it_end = cache.end();
    for (; it != it_end; ++it) {
        if ((*it)->pixmap == pixmap) {
            if ((*it)->count) {
                (*it)->count--;
                if (s_timed_cache) {
                    cleanCache();
                    return;
                }
            }

            if ((*it)->count <= 0)
                cleanCache();

            return;
        }
    }
}


void ImageControl::colorTables(const unsigned char **rmt, const unsigned char **gmt,
                               const unsigned char **bmt,
                               int *roff, int *goff, int *boff,
                               int *rbit, int *gbit, int *bbit) const {

    if (rmt) *rmt = red_color_table;
    if (gmt) *gmt = green_color_table;
    if (bmt) *bmt = blue_color_table;

    if (roff) *roff = red_offset;
    if (goff) *goff = green_offset;
    if (boff) *boff = blue_offset;

    if (rbit) *rbit = red_bits;
    if (gbit) *gbit = green_bits;
    if (bbit) *bbit = blue_bits;
}

void ImageControl::getGradientBuffers(unsigned int w,
                                      unsigned int h,
                                      unsigned int **xbuf,
                                      unsigned int **ybuf) {

    if (w > grad_xbuffer.size())
        grad_xbuffer.resize(w);

    if (h > grad_ybuffer.size())
        grad_ybuffer.resize(h);

    *xbuf = &grad_xbuffer[0];
    *ybuf = &grad_ybuffer[0];
}


void ImageControl::installRootColormap() {

    Display *disp = FbTk::App::instance()->display();
    XGrabServer(disp);

    bool install = true;
    int i = 0, ncmap = 0;
    Colormap *cmaps =
        XListInstalledColormaps(disp, RootWindow(disp, screenNumber()), &ncmap);

    if (cmaps) {
        for (i = 0; i < ncmap; i++) {
            if (*(cmaps + i) == m_colormap)
                install = false;
        }

        if (install)
            XInstallColormap(disp, m_colormap);

        XFree(cmaps);
    }

    XUngrabServer(disp);
}



void ImageControl::cleanCache() {
    Display *disp = FbTk::App::instance()->display();
    list<CacheList::iterator> deadlist;
    CacheList::iterator it = cache.begin();
    CacheList::iterator it_end = cache.end();
    for (; it != it_end; ++it) {
        Cache *tmp = (*it);
        if (tmp->count <= 0) {
            XFreePixmap(disp, tmp->pixmap);
            deadlist.push_back(it);
            delete tmp;
            tmp=0;
        }
    }

    list<CacheList::iterator>::iterator dead_it = deadlist.begin();
    list<CacheList::iterator>::iterator dead_it_end = deadlist.end();
    for (; dead_it != dead_it_end; ++dead_it) {
        cache.erase(*dead_it);
    }

}

void ImageControl::createColorTable() {
    Display *disp = FbTk::App::instance()->display();

    int count;
    XPixmapFormatValues *pmv = XListPixmapFormats(disp, &count);

    if (pmv) {
        bits_per_pixel = 0;
        for (int i = 0; i < count; i++) {
            if (pmv[i].depth == m_screen_depth) {
                bits_per_pixel = pmv[i].bits_per_pixel;
                break;
            }
        }

        XFree(pmv);
    }

    if (bits_per_pixel == 0)
        bits_per_pixel = m_screen_depth;

    red_offset = green_offset = blue_offset = 0;

    switch (visual()->c_class) {
    case TrueColor: {
        unsigned long red_mask = visual()->red_mask,
            green_mask = visual()->green_mask,
            blue_mask = visual()->blue_mask;

        while (! (red_mask & 1)) { red_offset++; red_mask >>= 1; }
        while (! (green_mask & 1)) { green_offset++; green_mask >>= 1; }
        while (! (blue_mask & 1)) { blue_offset++; blue_mask >>= 1; }

        red_bits = 255 / red_mask;
        green_bits = 255 / green_mask;
        blue_bits = 255 / blue_mask;

        initColortables(red_color_table, green_color_table, blue_color_table,
                red_bits, green_bits, blue_bits);
    }

        break;

    case PseudoColor:
    case StaticColor: {

        size_t num_colors = m_colors_per_channel * m_colors_per_channel * m_colors_per_channel;

        if (num_colors > static_cast<unsigned>(1 << m_screen_depth)) {
            m_colors_per_channel = (1 << m_screen_depth) / 3;
            num_colors = m_colors_per_channel * m_colors_per_channel * m_colors_per_channel;
        }

        if (m_colors_per_channel < 2 || num_colors > static_cast<unsigned>(1 << m_screen_depth)) {
            fprintf(stderr, "ImageControl::ImageControl: invalid colormap size %zd "
                    "(%d/%d/%d) - reducing",
                    num_colors, m_colors_per_channel, m_colors_per_channel,
                    m_colors_per_channel);

            m_colors_per_channel = (1 << m_screen_depth) / 3;
        }

        m_colors.resize(num_colors);

        int bits = 255 / (m_colors_per_channel - 1);
        red_bits = green_bits = blue_bits = bits;

        initColortables(red_color_table, green_color_table, blue_color_table,
                red_bits, green_bits, blue_bits);

        for (int r = 0, i = 0; r < m_colors_per_channel; r++) {
            for (int g = 0; g < m_colors_per_channel; g++) {
                for (int b = 0; b < m_colors_per_channel; b++, i++) {
                    m_colors[i].red = (r * 0xffff) / (m_colors_per_channel - 1);
                    m_colors[i].green = (g * 0xffff) / (m_colors_per_channel - 1);
                    m_colors[i].blue = (b * 0xffff) / (m_colors_per_channel - 1);
                    m_colors[i].flags = DoRed|DoGreen|DoBlue;
                }
            }
        }

        allocateUnallocatedColors(m_colors, disp, m_colormap, m_screen_depth);
        break;
    }

    case GrayScale:
    case StaticGray:
        {
            size_t num_colors;

            if (visual()->c_class == StaticGray) {
                num_colors = 1 << m_screen_depth;
            } else {
                num_colors = m_colors_per_channel * m_colors_per_channel * m_colors_per_channel;

                if (num_colors > static_cast<unsigned>(1 << m_screen_depth)) {
                    m_colors_per_channel = (1 << m_screen_depth) / 3;
                    num_colors = m_colors_per_channel * m_colors_per_channel * m_colors_per_channel;
                }
            }

            if (m_colors_per_channel < 2 || num_colors > static_cast<unsigned>(1 << m_screen_depth)) {
                fprintf(stderr,"FbTk::ImageControl: invalid colormap size %zd "
                        "(%d/%d/%d) - reducing",
                        num_colors, m_colors_per_channel, m_colors_per_channel,
                        m_colors_per_channel);

                m_colors_per_channel = (1 << m_screen_depth) / 3;
            }

            m_colors.resize(num_colors);

            int bits = 255 / (m_colors_per_channel - 1);
            red_bits = green_bits = blue_bits = bits;

            initColortables(red_color_table, green_color_table, blue_color_table,
                red_bits, green_bits, blue_bits);

            for (unsigned int i = 0; i < num_colors; i++) {
                m_colors[i].red = (i * 0xffff) / (m_colors_per_channel - 1);
                m_colors[i].green = (i * 0xffff) / (m_colors_per_channel - 1);
                m_colors[i].blue = (i * 0xffff) / (m_colors_per_channel - 1);
                m_colors[i].flags = DoRed|DoGreen|DoBlue;
            }

            allocateUnallocatedColors(m_colors, disp, m_colormap, m_screen_depth);
            break;
        }

    default:
        _FB_USES_NLS;
        cerr<<"FbTk::ImageControl: "<<_FBTK_CONSOLETEXT(Error, UnsupportedVisual, "Unsupported visual", "A visual is a technical term in X")<<endl;
        break;
    }
}

} // end namespace FbTk
