/*
 * Copyright (C) 2009-2011 Robert Gerlach
 *
 * You can redistribute and/or modify this program under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

#include "fjbtndrv.h"
#include "rotation.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <X11/extensions/Xrandr.h>

typedef struct _scriptlist {
	char name[PATH_MAX];
	struct _scriptlist * next;
} scriptlist;

static int is_script(const char *filename)
{
	struct stat s;
	char buffer[PATH_MAX];
	int error;
	int len = strlen(filename);

	if((filename[0] != '.') &&
	   (filename[len-1] != '~') &&
	   !strcasecmp(&(filename[len-4]), ".bak") ||
	   !strcasecmp(&(filename[len-4]), ".old"))
		return 0;

	error = stat(filename, &s);
	if(error)
		return 0;

	if((s.st_mode & S_IFMT) == S_IFREG) {
		return !!(s.st_mode & S_IXUSR);
	}

	else if((s.st_mode & S_IFMT) == S_IFLNK) {
		len = readlink(filename, buffer, PATH_MAX-1);
		if(len > 0) {
			buffer[len] = '\0';
			return is_script(buffer);
		} else
			perror(filename);
	}
	return 0;

}

static scriptlist* find_scripts(void)
{
	DIR *dh;
	struct dirent *de;
	int len;
	char *homedir, buffer[PATH_MAX];
	scriptlist *paths, **next;

	paths = NULL;
	next = &paths;

	homedir = getenv("HOME");
	if(homedir) {
		len = snprintf(buffer, PATH_MAX, "%s/." PACKAGE "/scripts", homedir);
		if(len > 0) {
			dh = opendir(buffer);
			if(dh) {
				buffer[len++] = '/';

				while((de = readdir(dh))) {
					if((!de->d_name) || (de->d_name[0] == '.'))
						continue;

					strncpy(&(buffer[len]), de->d_name, PATH_MAX - len);
					if(is_script(buffer)) {
						*next = malloc(sizeof(scriptlist));
						strcpy((*next)->name, buffer);
						next = &((*next)->next);
					}
				}

				closedir(dh);
			}
		}
	}

	len = snprintf(buffer, PATH_MAX, "%s", SCRIPTDIR);
	if(len > 0) {
		dh = opendir(SCRIPTDIR);
		if(dh) {
			buffer[len++] = '/';

			while((de = readdir(dh))) {
				if((!de->d_name) || (de->d_name[0] == '.'))
					continue;

				strncpy(&(buffer[len]), de->d_name, PATH_MAX - len);
				if(is_script(buffer)) {
					*next = malloc(sizeof(scriptlist));
					strcpy((*next)->name, buffer);
					next = &((*next)->next);
				}
			}

			closedir(dh);
		}
	}

	(*next) = NULL;
	return paths;
}

static void free_scriptlist(scriptlist* list)
{
	if(!list)
		return;

	if(list->next)
		free_scriptlist(list->next);

	free(list);
}

static int run_scripts(Rotation current_orientation, Rotation orientation,
		       int mode, int pre_post)
{
	int error;
       	scriptlist *paths, *path;


	paths = find_scripts();
	if(!paths)
		return 0;

	setenv("CURRENT_ORIENTATION", r2s(current_orientation), 1);
	setenv("ORIENTATION", r2s(orientation), 1);
	setenv("ACTION", pre_post ? "rotated" : "rotating", 1);

	if(mode >= 0)
		setenv("MODE", m2s(mode), 1);

	debug("env: CURRENT_ORIENTATION=%s ORIENTATION=%s ACTION=%s MODE=%s",
			getenv("CURRENT_ORIENTATION"),
			getenv("ORIENTATION"),
			getenv("ACTION"),
			getenv("MODE"));

	path = paths;
	do {
		int pid = fork();
		if(pid == 0) {
			debug("exec: %s", path->name);
			execl(path->name, path->name, NULL);
		} else {
			waitpid(pid, &error, 0);
			if (error)
				debug("      script failed with %d.", error);
		}
	} while((!error) && (path = path->next));

	free_scriptlist(paths);
	return error;
}

char* r2s(Rotation r)
{
	switch(r & 0x0f) {
		case RR_Rotate_0:
			return "normal";
		
		case RR_Rotate_90:
			return "left";

		case RR_Rotate_180:
			return "inverted";

		case RR_Rotate_270:
			return "right";

		default:
			return "";
	}
}

char* m2s(int mode)
{
	if(mode)
		return "tablet";
	else
		return "normal";
}

static inline
void _rotate(Display *display, XRRScreenResources *rs, XRROutputInfo *oi, Rotation r)
{
	XRRCrtcInfo *ci;
	int screen, s_width = 0, s_height = 0;
	int resize_needed = 0;

	screen = DefaultScreen(display);
	debug("current screen size: %dx%d", s_width, s_height);

	if (oi->ncrtc == 0)
		return;

	if (oi->ncrtc > 1)
		fprintf(stderr, "several CRTCs are not yet supported\n");

	ci = XRRGetCrtcInfo(display, rs, oi->crtcs[0]);
	if (!ci) return;

	if (ci->rotation != r) {
		debug("Rotate %s from %s to %s!", oi->name,
				r2s(ci->rotation), r2s(r));
		XRRSetCrtcConfig(display, rs, oi->crtcs[0], CurrentTime,
				ci->x, ci->y, ci->mode, r,
				ci->outputs, ci->noutput);
	}

	if (ci->rotation & (RR_Rotate_0 | RR_Rotate_180)) {
		if (r & (RR_Rotate_90 | RR_Rotate_270)) {
			resize_needed = 1;
			s_width = DisplayWidth(display, screen);
			s_height = DisplayHeight(display, screen);
		}
	} else {
		if (r & (RR_Rotate_0 | RR_Rotate_180)) {
			resize_needed = 1;
			s_width = DisplayHeight(display, screen);
			s_height = DisplayWidth(display, screen);
		}
	}

	debug ("CRTC 1: %dx%d %dx%d %dx%d", ci->x, ci->y,
			ci->width, ci->height, s_width, s_height);

	if ((resize_needed) && (ci->x == 0) && (ci->y == 0) &&
	    (ci->width == s_width) && (ci->height == s_height)) {
		debug("resizing screen to: %dx%d", s_height, s_width);
		XRRSetScreenSize(display, DefaultRootWindow(display),
				s_height, s_width,
				DisplayWidthMM(display, screen),
				DisplayHeightMM(display, screen));
	}

	XRRFreeCrtcInfo(ci);
}

static inline
Rotation _current_rotation(Display *display, XRRScreenResources *rs, XRROutputInfo *oi)
{
	XRRCrtcInfo *ci;
	Rotation ret = RR_Rotate_0;
	int c;

	for (c = 0; c < oi->ncrtc; c++) {
		ci = XRRGetCrtcInfo(display, rs, oi->crtcs[c]);
		if (!ci) continue;

		ret = ci->rotation;

		XRRFreeCrtcInfo(ci);
		break;
	}

	return ret;
}

void rotate_display(Display *display, Rotation rr, int mode)
{
	Window rw;
	XRRScreenResources  *rs;
	XRROutputInfo *oi;
	Rotation cr = RR_Rotate_0;
	int o;

	rw = DefaultRootWindow(display);

	rs = XRRGetScreenResources(display, rw);
	if(!rs) return;

	/* get the current rotation (from first output only) */
	for (o = 0; o < rs->noutput; o++) {
		oi = XRRGetOutputInfo (display, rs, rs->outputs[o]);
		if (!oi) continue;

		if (strncmp(oi->name, "LVDS", 4) != 0) {
			XRRFreeOutputInfo(oi);
			continue;
		}

		cr = _current_rotation(display, rs, oi);
		XRRFreeOutputInfo(oi);
		break;
	}

	if (!rr) {
		rr = (cr & 0x7) << 1;
		if(!rr)
			rr = RR_Rotate_0;
	}

	/* already in the right orientation? */
	if (cr == rr)
		return;

	/* pre-rotation scripts */
	if (run_scripts(cr, rr, mode, 0) != 0)
		goto err;

	/* rotate all LVDS screens */
	for (o = 0; o < rs->noutput; o++) {
		oi = XRRGetOutputInfo (display, rs, rs->outputs[o]);
		if (!oi) continue;

		if (oi->connection != RR_Connected)
			continue;

		if (strncmp(oi->name, "LVDS", 4) == 0)
			_rotate(display, rs, oi, rr);

/*** 
 * rotate cloned display as well?
		for (int c = 0; c < oi->nclone; c++) {
			XRROutputInfo *ci = XRRGetOutputInfo(display, rs, oi->clones[c]);
			if (!ci) continue;
	
			_rotate(display, rs, ci, rr);
		}
 ***/

		XRRFreeOutputInfo(oi);
	}

	/* post-rotation scripts */
	if (run_scripts(cr, rr, mode, 1) != 0)
		goto err;

  err:
	XRRFreeScreenResources(rs);
}

