/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "plugutil.h"
#include <regex.h>

typedef enum wcdef_types
{
   simple = 0,
   regex  = 1
} wcdef_types_t;

/* default flags and the like for a WM_CLASS */
typedef struct wcdefs
{
   /* dictates which of the compare variables to use */
   int      type;

   /* Used if type is regex */
   char     *pattern;
   
   /* Used if type is simple */
	char		*res_name;	/* resource name */
	char		*res_class;	/* pointer into res_name, don't free() it */

	/* dgroup to use, or null */
	dgroup_t	*dgroup;

	/* stacking layer to put the client in */
	int		stacklayer;

	/* these correspond to clientflags_t, read client.h */
	int		nofocus;
	int		noresize;
	int		nomove;
	int		noiconify;
	int		nodelete;
	int		sticky;
   int      skip_pager;
   

	SLIST_ENTRY(wcdefs) w_list;
} wcdefs_t;

/* list of wcdefs */
static SLIST_HEAD(, wcdefs) wcdefs_list = SLIST_HEAD_INITIALIZER(wcdefs_list);

#define SLENGTH 128

int match( char *pattern, char *string )
{
   char	   message[SLENGTH];
   char	   *start_search;
   int	   error, msize, count;
   regex_t  preg;
   regmatch_t pmatch;

   error = regcomp( &preg, pattern,
                    REG_ICASE | REG_EXTENDED);
   if (error)
   {
      msize = regerror(error, &preg, message, SLENGTH);
      log( 3, "%s\n", message);
      if (msize > SLENGTH)
      {
		   log( 3,  "Additional text lost\n");
      }
      return 0;
   }
   
   error = regexec(&preg, string, 1, &pmatch, 0);
   
   if (error == REG_NOMATCH)
   {
      log( 3,  "No matches in string\n" );
      return 0;
   }
   else if (error != 0)
   {
      msize = regerror(error, &preg, message, SLENGTH);
      log( 3, "%s\n", message);
      if (msize > SLENGTH)
      {
		   log( 3,  "Additional text lost\n");
      }
      return 0;
   };
   
   count = 1;
   start_search = string + pmatch.rm_eo;
   while (error == 0)
   {
      error =
          regexec(&preg, start_search, 1, &pmatch,
                  REG_NOTBOL);
      start_search = start_search + pmatch.rm_eo;
      count++;
   };
   count--;
   log( 3,  "There are %i matches\n", count);
   regfree(&preg);

   return 1; // -------->
}


static wcdefs_t *wcdefs_entry_exists( char *res_name,
                                      char *res_class )
{
	wcdefs_t *wcdefs;
             
   /*
     If the regex flag is set, then the entries in wcdefs->res_class
     and wcdefs->res_name should be used as a regular expression
     when comparing to the class hints.

     If the regex compare doesn't work, we should try the literal
     compare. Either that or when creating the entries we make sure they
     are all valid regex's .... I like the 2nd
         
   */
   /* loop through and cmp the strings */
   SLIST_FOREACH(wcdefs, &wcdefs_list, w_list)
       {          
          if( wcdefs->type == simple )
          {
             if( wcdefs->res_class != NULL &&
                 wcdefs->res_name != NULL &&
                 res_class != NULL &&
                 res_name != NULL )
             {
                if (strcmp(wcdefs->res_class, res_class) == 0)
                   if (strcmp(wcdefs->res_name, res_name) == 0)
                      goto found; //--------->
             }
          }
          else
          {
             if( wcdefs->pattern != NULL &&
                 res_name != NULL )
             {
                /* pattern comes in via the first parameter only */
                if (strcmp( wcdefs->pattern, res_name ) == 0)
                {
                   goto found; //--------->
                }
             }
          }
       }      
  

   /* This is skipped if a match is found .... */
   return NULL; // ------------->

found:
	return wcdefs;
}

/* find a wcdefs based on a res_name and res_class */
static wcdefs_t *wcdefs_find( char *res_name,
                              char *res_class )
{
	wcdefs_t *wcdefs;
   char compare[512];
             
   /*
     If the regex flag is set, then the entries in wcdefs->res_class
     and wcdefs->res_name should be used as a regular expression
     when comparing to the class hints.

     If the regex compare doesn't work, we should try the literal
     compare. Either that or when creating the entries we make sure they
     are all valid regex's .... I like the 2nd
         
   */
   /* loop through and cmp the strings */
   SLIST_FOREACH(wcdefs, &wcdefs_list, w_list)
       {
          if( wcdefs->type == simple )
          {
             if( wcdefs->res_class != NULL &&
                 wcdefs->res_name != NULL &&
                 res_class != NULL &&
                 res_name != NULL )
             {
                log( 3, "simple compare ...\n");
                if (strcmp(wcdefs->res_class, res_class) == 0)
                   if (strcmp(wcdefs->res_name, res_name) == 0)
                      goto found;
             }
          }
          else
          {
             if( wcdefs->pattern != NULL &&
                 res_name != NULL )
             {
                if( strlen( wcdefs->pattern ) < 255 &&
                    strlen( res_class ) + strlen( res_name ) < 512 )
                {
                   /* change to strncpy, etc */
                   strcpy( compare, res_name );
                   if( strlen( res_class ) > 0 )
                   {
                      strcat( compare, res_class );
                   }

                   log( 3, ".........comparing: %s to pattern %s\n",
                          compare,
                          wcdefs->pattern );
                   if( match( wcdefs->pattern, compare ) == 1 )
                   {
                      goto found; // hate doing this goto, but when in Rome ...
                   }
                }
             }
          }
       }      
  

   /* This is skipped if a match is found .... */
   return NULL; // ------------->

found:
	return wcdefs;
}

/* handle default setting on newly arriving clients */
static int init_hints(int pcall, client_t *client)
{
	wcdefs_t *wcdefs;

	/* find an appropriate wcdefs */
	wcdefs = wcdefs_find( client->classhint.res_name,
                         client->classhint.res_class );
	if (!wcdefs)
		return PLUGIN_OK;

	/*
	 * set flags to our wcdefs flags.  don't touch flags that the user didn't
	 * say anything about in their rc.
	 */
	if (wcdefs->nofocus != -1)
		client->flags.nofocus = wcdefs->nofocus;
	if (wcdefs->noresize != -1)
		client->flags.noresize = wcdefs->noresize;
	if (wcdefs->nomove != -1)
		client->flags.nomove = wcdefs->nomove;
	if (wcdefs->noiconify != -1)
		client->flags.noiconify = wcdefs->noiconify;
	if (wcdefs->nodelete != -1)
		client->flags.nodelete = wcdefs->nodelete;
	if (wcdefs->sticky != -1)
		client->flags.sticky = wcdefs->sticky;
   if (wcdefs->skip_pager!= -1)
      client->flags.internal = wcdefs->skip_pager;

	/* set dgroup if we got one specified */
	if (wcdefs->dgroup)
		client->dgroup = wcdefs->dgroup;

	/* set stacking layer if it was specified */
	if (wcdefs->stacklayer != -1)
		client->stacklayer = wcdefs->stacklayer;

	return PLUGIN_OK;
}

/* build our defaults information structures */
int wmclass_defs_init() {
	wcdefs_t *wcdefs;
	param_t *param;
	char *res_name, *res_class, *pat;
	int i;

	/* get all the wcdefs_t's built */
	SUBPARAMS_FOREACH(i, param, &plugin_this->params)
   {
		if (strcmp(param->name, "regex" ) == 0)
      {
         log( 3,  "adding a regex entry: %s\n", param->value );
         pat = strdup(param->value);
         if (!pat)
         {
            PERR("no memory to strdup regex");
         }

         wcdefs = wcdefs_entry_exists( pat, NULL );
         if (!wcdefs)
         {
            wcdefs = malloc(sizeof(wcdefs_t));
            if (!wcdefs)
            {
               free(res_name);
               PERR("no memory for wcdefs");
            }
            wcdefs->type = regex;
            wcdefs->pattern = pat;          
         }
         else
         {
            free( pat );
            return PLUGIN_UNLOAD; // -------->
         }  
      }
      if (strcmp(param->name, "def") == 0)
      {
         log( 3, "adding a simple entry\n");
         /* get the res_name.res_class pair from the value */
         res_name = strdup(param->value);
         if (!res_name)
         {
            PERR("no memory to strdup res_name.res_class");
         }
         res_class = strrchr(res_name, '.');
         if (*res_class != '\0')
            *res_class++ = '\0';
      
         /* see if a entry exists, or make one */
         wcdefs = wcdefs_entry_exists( res_name, res_class );
         if (!wcdefs)
         {
            wcdefs = malloc(sizeof(wcdefs_t));
            if (!wcdefs)
            {
               free(res_name);
               PERR("no memory for wcdefs");
            }

            /* init the structure */
            wcdefs->type = simple;
            
            wcdefs->res_name = res_name;
            wcdefs->res_class = res_class;
         }
         else
         {
            free(res_name);
            return PLUGIN_UNLOAD; // -------->
         }
      }

      log( 3, "configuring entry ....\n");
      wcdefs->dgroup = NULL;
      wcdefs->stacklayer = -1;
      wcdefs->nofocus = -1;
      wcdefs->noresize = -1;
      wcdefs->nomove = -1;
      wcdefs->noiconify = -1;
      wcdefs->nodelete = -1;
      wcdefs->sticky = -1;
      wcdefs->skip_pager = -1;
      SLIST_INSERT_HEAD(&wcdefs_list, wcdefs, w_list);
      		
      /* now read for anything they set */
      OPTIONAL_PARAM(&param->subparams, "nofocus", bool, wcdefs->nofocus, wcdefs->nofocus);
      OPTIONAL_PARAM(&param->subparams, "noresize", bool, wcdefs->noresize, wcdefs->noresize);
      OPTIONAL_PARAM(&param->subparams, "nomove", bool, wcdefs->nomove, wcdefs->nomove);
      OPTIONAL_PARAM(&param->subparams, "noiconify", bool, wcdefs->noiconify, wcdefs->noiconify);
      OPTIONAL_PARAM(&param->subparams, "nodelete", bool, wcdefs->nodelete, wcdefs->nodelete);
      OPTIONAL_PARAM(&param->subparams, "sticky", bool, wcdefs->sticky, wcdefs->sticky);
      OPTIONAL_PARAM(&param->subparams, "skip_pager", bool, wcdefs->skip_pager, wcdefs->skip_pager);
      
      OPTIONAL_PARAM(&param->subparams, "dgroup", dgroup, wcdefs->dgroup, wcdefs->dgroup);
      OPTIONAL_PARAM(&param->subparams, "stacklayer", stacklayer, wcdefs->stacklayer, wcdefs->stacklayer);
	}

	return PLUGIN_OK;
}

/* register our callback */
int wmclass_defs_start() {
	plugin_callback_add(plugin_this, PCALL_INIT_HINTS, init_hints);
	return PLUGIN_OK;
}

/* free our wcdefs */
void wmclass_defs_shutdown() {
	wcdefs_t *wcdefs, *next;

	wcdefs = SLIST_FIRST(&wcdefs_list);
	while (wcdefs) {
		/* don't free res_class, it's just a ptr into res_name */
		free(wcdefs->res_name);
		next = SLIST_NEXT(wcdefs, w_list);
		free(wcdefs);
		wcdefs = next;
	}
}

/* stuff we don't care about */
void wmclass_defs_xevent_handler(XEvent *e) {}
