/* Clementine Window Manager
   Copyright 2002 Dave Berton <db@mosey.org>

   based on aewm Copyright 1998-2001 Decklin Foster <decklin@red-bean.com>

   This program is free software; see LICENSE for details. */

#include "look.h"
#include "tokenizer.hpp"
#include <iostream>
#include <fstream>
#include <cassert>
#include <memory>
#include <vector>
#include <algorithm>


RGBColor::RGBColor( unsigned int r, unsigned int g, unsigned int b,
	      unsigned long p )
    : red(r), green(g), blue(b), pix(p)
{
}

RGBColor::~RGBColor()
{
}

RGBColor::RGBColor( const RGBColor& other )
{
    *this = other;
}

RGBColor& RGBColor::operator=( const RGBColor& other )
{
    red = other.red;
    green = other.green;
    blue = other.blue;
    pix = other.pix;
    return *this;
}

	    
unsigned long RGBColor::pixel() const 
{ 
    return pix; 
}

///////////

ColorManager::ColorManager( Display* d, int s )
    : dpy(d), screen(s)
{
    assert( dpy );
}

ColorManager::~ColorManager()
{
    if ( colors.size() ) {
	unsigned long *pixels = new unsigned long [colors.size()];
	unsigned int i;
	for ( i = 0; i < colors.size(); ++i )
	    *(pixels + i) = colors[i].pixel();
	XFreeColors(dpy, DefaultColormap(dpy, screen), 
		    pixels, colors.size(), 0);
	// can't check for error here, although an X error will occur
	// if there is a problem.
	delete [] pixels;
    }
}

RGBColor ColorManager::alloc(unsigned int r, 
			     unsigned int g, 
			     unsigned int b)
{
    /* find it */
    std::vector<RGBColor>::const_iterator it = colors.begin();
    for ( ; it != colors.end(); ++it ) {
	unsigned int red, green, blue;
	(*it).RGB( red, green, blue );
	if ( red == r && green == g && blue == b ) {
	    return *it;
	}
    }
    /* otherwise, allocate it */
    XColor c;
    c.red = r;
    c.green = g;
    c.blue = b;
    if (!XAllocColor( dpy, DefaultColormap(dpy, screen), &c ) )
	std::cerr << "Could not allocate r:" << 
	    (unsigned int)r << " g:" << (unsigned int)g << 
	" b:" << (unsigned int)b << std::endl;

    // note! save the -originally- requested RGD values so that
    // subsequent lookups will return the same color.  XAllocColor can
    // possible change these values to a similar matching color
    // depending on the server we are running under.
    RGBColor rgbc( r, g, b, c.pixel ); 
    colors.push_back( rgbc ); 
    
    return rgbc;
}

RGBColor ColorManager::darken( const RGBColor& c )
{
    unsigned int r, g, b;
    c.RGB( r, g, b );
    r = (r*2/3);
    g = (g*2/3);
    b = (b*2/3);
    RGBColor d = alloc( r, g, b );
    return d;
}

RGBColor ColorManager::lighten( const RGBColor& c )
{
    unsigned int r, g, b;
    c.RGB( r, g, b );
    if ( r + g + b == 0 ) /* special case, black */
	return alloc( 180*256, 180*256, 180*256 );
    r = r + (r/3);
    if ( r > 65535 )
	r = 65535;
    g = g + (g/3);
    if ( g > 65535 )
	g = 65535;
    b = b + (b/3);
    if ( b > 65535 )
	b = 65535;
    RGBColor l = alloc( r, g, b );
    return l;
}

///////////

FacetParser::FacetParser( Display* d, int s, const std::string& name,
	      ColorManager* colormanager )
    : dpy(d), screen(s), nm(name), cm(colormanager)
{
    assert(dpy);
}

FacetParser::FacetParser( const FacetParser& other )
    : Facet( other ),
      dpy( other.dpy ), 
      screen( other.screen ),
      nm( other.nm )
{
}

FacetParser& FacetParser::operator=( const FacetParser& other )
{
    Facet::operator=(other);
    dpy = other.dpy;
    screen =  other.screen;
    nm = other.nm;
    styl = other.styl;
    back = other.back;
    return *this;
}

FacetParser::~FacetParser()
{
}

void FacetParser::parse( std::vector<std::string> setting )
{
    if ( setting.size() == 0 )
	return;
    std::vector<std::string> property;
    std::string propName = propertize( setting, property );
    if ( !propName.length() ) 
	return;
    if ( propName == "style" ) 
	parseStyle( property );
    if ( propName == "color" )
	parseColor( property );
    if ( propName == "background" )
	parseBackground( property );
    if ( propName == "border" )
	parseBorder( property );
}

std::string FacetParser::propertize( std::vector<std::string> in, 
			       std::vector<std::string>& out )
{
    /* 'in' is of the form:
       name.property[.subproperty...] setting [setting...] 
    */
    if ( ! in.size() >= 2 ) {
	std::cerr << "Bad property" << std::endl;
	return "";
    }
    /* parse the main property we are interested in */
    std::vector<std::string> prop;
    tokenize( in[0], std::string("."), prop );
    if ( !prop.size() > 2 ) {
	std::cerr << "Unable to parse property:" << in[0] << std::endl;
	return "";
    }
    if ( prop[0] != nm ) /* must be my property */
	return "";
    std::string mainProperty = prop[1];
    if ( prop.size() > 2 )
	out.push_back( prop[2] );
    std::vector<std::string>::const_iterator it = in.begin();
    for ( ++it; it != in.end(); ++it ) 
	out.push_back( *it );
    return mainProperty;
 }

void FacetParser::parseStyle( const std::vector<std::string>& s )
{
    std::vector<std::string>::const_iterator it;
    for ( it=s.begin(); it!=s.end();++it) {
	if ( *it == "gradient" )
	    styl.setIsGradient( true );
	if ( *it == "flat" )
	    styl.setIsFlat( true );
	if ( *it == "bevel" )
	    styl.setIsBevel( true );
	if ( *it == "horizontal" )
	    styl.setIsHorizontal( true );
	if ( *it == "vertical" )
	    styl.setIsVertical( true );
	if ( *it == "ascend" )
	    styl.setIsAscending( true );
	if ( *it == "descend" )
	    styl.setIsDescending( true );
    }
}

void FacetParser::parseColor( const std::vector<std::string>& b )
{
    parseColor( colr, b );
}

void FacetParser::parseBackground( const std::vector<std::string>& b )
{
    parseColor( back, b );
}

void FacetParser::parseBorder( const std::vector<std::string>& b )
{
    parseColor( bord, b );
}

void FacetParser::parseColor( ColorSet& cs, 
			      const std::vector<std::string>& b )
{
    if ( b.size() == 1 ) { /* just the color */
	cs.color = parseColor( b[0] );
	cs.light = cm->lighten( back.color );
	cs.dark = cm->darken( back.color );
	return;
    }
    std::vector<std::string>::const_iterator it;
    for ( it=b.begin(); it!=b.end();++it) {
	if ( *it == "from" ) 
	    cs.light = parseColor( b[b.size()-1] );
	if ( *it == "to" )
	    cs.dark = parseColor( b[b.size()-1] );
    }
}

RGBColor FacetParser::parseColor( const std::string& name ) const
{
    RGBColor c;
    if ( !name.length() ) {
	std::cerr << "Cannot parse empty color" << std::endl;
	return c;
    }
    XColor color;
    // db more error check
    if ( XParseColor( dpy, DefaultColormap( dpy, screen ), (char*)name.c_str(),
		      &color) ) {
	c = cm->alloc( color.red, color.green, color.blue );
    } else {
	std::cerr << "Could not parse color:" << name << std::endl;
    }
    return c;
}

///////////

Look::Look( Display* d, int screen )
    : cm(d, screen),
      active( d, screen, "active", &cm ),
      activeButton( d, screen, "button", &cm ),
      inactive( d, screen, "inactive", &cm ),
      inactiveButton( d, screen, "button", &cm )
{
    if ( !parse( "look" ) ) //db more add other search paths
	setDefaults();
}

Look::~Look()
{
}

bool Look::parse( const std::string& name )
{
    std::ifstream file( name.c_str() );
    if ( !file.is_open() ) { 
	std::cerr << "Error opening file '" << name << "'" << std::endl; 
	return false;
    }

    std::auto_ptr<char> linebuffer(new char[1024]);
	
//  int line=0;

    while (!file.eof()) {	
	file.getline(linebuffer.get(), 1024);		
	std::string line( linebuffer.get() );
	if ( !line.length() )
	    continue;
	std::vector<std::string> property;
	tokenize( line, std::string(" "), property );
	if ( !property.size() || property[0][0] == '#' )
	    continue; // skip comments and blank lines
	if ( property.size() > 0 ) {
	    if ( property[0] == "background" ) {
		rootCmd = "";
		std::vector<std::string>::const_iterator it = 
		    property.begin();
		for ( ++it; it != property.end(); ++it ) 
		    rootCmd += " " + *it;
		rootCmd = rootCmd.substr( 1, rootCmd.length()-1 );
	    } else if ( property[0].substr(0,13) == "active.button" ) {
		property[0] = property[0].substr( 7, property[0].length() );
		activeButton.parse( property );
	    } else if ( property[0].substr(0,15) == "inactive.button" ) {
		property[0] = property[0].substr( 9, property[0].length() );
		inactiveButton.parse( property );
	    } else if ( property[0].substr(0,6) == "active" )
		active.parse( property );
	    else if ( property[0].substr(0,8) == "inactive" )
		inactive.parse( property );
	}
    }
    
    return true;
}

void Look::setDefaults()
{
    /* simple grey theme */
    Style s;
    s.setIsGradient( true );
    s.setIsVertical( true );
    //s.setIsBevel( true );
    active.setStyle( s );
    inactive.setStyle( s );

    ColorSet c;
    c.color = cm.alloc( 0, 0, 0 );
    c.light = cm.alloc( 206*256, 206*256, 206*256 );
    c.dark = cm.alloc( 169*256, 169*256, 169*265 ); 
    active.setColor( c );
    inactive.setColor( c );
    ColorSet b;
    b.color = cm.alloc( 0, 0, 0 );
    b.light = cm.lighten( b.color );
    b.dark = cm.darken( b.color );

    ColorSet o;
    o.color = cm.alloc( 230*256, 230*256, 230*256 );
    o.light = cm.lighten( o.color );
    o.dark = cm.darken( o.color );
    active.setBackground( o );
    active.setColor( o );
    inactive.setColor( o );
    inactive.setBackground( o );
    rootCmd = "xsetroot -solid grey30";

}


syntax highlighted by Code2HTML, v. 0.9.1