/*  Tux Commander VFS: String utilities
 *  Copyright (C) 2007-2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
 *   Check for updates on tuxcmd.sourceforge.net
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>

#include "treepathutils.h"
#include "strutils.h"
#include "vfsutils.h"






struct PathTree* filelist_tree_new()
{
  struct PathTree *tree = (struct PathTree*)malloc(sizeof(struct PathTree));
  memset(tree, 0, sizeof(struct PathTree));
  log("filelist_tree_new()\n");
  tree->items = g_ptr_array_new();
  tree->data = NULL;
  tree->index = 0;
  tree->node = strdup("/");
  tree->original_pathstr = NULL;

  //  create placeholder data
  tree->data = (struct TVFSItem*)malloc(sizeof(struct TVFSItem));
  memset(tree->data, 0, sizeof(struct TVFSItem));
  tree->data->FName = strdup(tree->node);
  tree->data->FDisplayName = strdup(tree->node);
  tree->data->ItemType = vDirectory;
  tree->data->iMode = S_IRWXO + S_IRWXG + S_IRWXU;
  tree->data->iUID = geteuid();
  tree->data->iGID = getegid();
  tree->data->m_time = time(NULL);
  tree->data->c_time = tree->data->m_time;
  tree->data->a_time = tree->data->m_time;

  return tree;
}


void filelist_tree_free(struct PathTree *tree)
{
  if (! tree) {
	  fprintf(stderr, "filelist_tree_free: tree == NULL !\n");
	  return;
  }
  if (tree)
  {
	  if ((tree->items) && (tree->items->len > 0)) {
		  unsigned int i;
		  for (i = 0; i < tree->items->len; i++)
			  filelist_tree_free((struct PathTree*)g_ptr_array_index(tree->items, i));
	  }
	  if (tree->items)
		  g_ptr_array_free(tree->items, TRUE);
	  if (tree->data) {
		  if (tree->data->FName) free(tree->data->FName);
		  if (tree->data->FDisplayName) free(tree->data->FDisplayName);
		  if (tree->data->sLinkTo) free(tree->data->sLinkTo);
		  free(tree->data);
	  }
	  if (tree->node)
		  free(tree->node);
          if (tree->original_pathstr)
                  free(tree->original_pathstr);
	  free(tree);
  }
}


void filelist_tree_print_recurr(struct PathTree *tree, int level)
{
	if (tree) {
		char *fill = g_strnfill(level * 2, 32);
		printf("  %s#%lu. \"%s\"\n", fill, tree->index, tree->node);

		if ((tree->items) && (tree->items->len > 0)) {
			unsigned int i;
			for (i = 0; i < tree->items->len; i++)
			{
				struct PathTree* t = (struct PathTree*)g_ptr_array_index(tree->items, i);
				filelist_tree_print_recurr(t, level + 1);
			}
		}

		g_free(fill);
	}
}


void filelist_tree_print(struct PathTree *tree)
{
  filelist_tree_print_recurr(tree, 0);
}


struct PathTree* filelist_tree_find_node_by_path(struct PathTree *tree, const char *path)
{
  //  remove leading './'
  if (strstr(path, "./") == path)  path += 2;

  //  remove leading and trailing '/' if present
  char *p;
  if (IS_DIR_SEP(*path)) p = exclude_trailing_path_sep(path + 1);
  else p = exclude_trailing_path_sep(path);

  log("  filelist_tree_find_node_by_path: path = '%s', p = '%s'\n", path, p);
//  log("xx: '%s', '%s'\n", tree->node, path);

  struct PathTree* node = NULL;
  if ((tree) && (tree->node) && (strcmp(tree->node, "/") == 0) && (strcmp(path, "/") == 0)) {
	  log("  filelist_tree_find_node_by_path: matched root '/' element, returning.\n");
	  node = tree;
  } else
  if ((tree) && (tree->items) && (tree->items->len > 0)) {
	  //  split path
	  char *pos = strstr(p, "/");
	  char *first_part;
	  char *last_part;
	  if (pos) {
		  first_part = strndup(p, pos - p);
		  if (strlen(pos + 1) > 0) last_part = strdup(pos + 1);
		  else last_part = NULL;
	  } else {
		  first_part = strdup(p);
		  last_part = NULL;
	  }
	  log("   filelist_tree_find_node_by_path: pos = '%s'\n", pos);
	  log("   filelist_tree_find_node_by_path: first_part = '%s'\n", first_part);
	  log("   filelist_tree_find_node_by_path: last_part = '%s'\n", last_part);

	  //  find existing node
	  unsigned int i;
	  for (i = 0; i < tree->items->len; i++)
	  {
		  struct PathTree* t = (struct PathTree*)g_ptr_array_index(tree->items, i);
		  if (strcmp(t->node, first_part) == 0) {
			  //  this is the final station
			  if (! last_part) {
				  node = t;
				  log("  filelist_tree_find_node_by_path: found final node '%s', returning.\n", t->node);
			  } else
			  //  recurse to child items
			  if ((t->items) && (last_part)) {
				  log("  filelist_tree_find_node_by_path: found node '%s', recursing in deep.\n", t->node);
				  node = filelist_tree_find_node_by_path(t, last_part);
			  } else {
				  //  item found but no subitems
				  log("  filelist_tree_find_node_by_path: found node '%s', but no subitems found.\n", t->node);
			  }

			  break;
		  }
      }
	  free(first_part);
	  free(last_part);
  }
  free(p);
  return node;
}

unsigned long int filelist_find_index_by_path(struct PathTree *tree, const char *path)
{
  struct PathTree* node = filelist_tree_find_node_by_path(tree, path);
  if (node) return node->index;
  else return 0;
}

void filelist_tree_add_item_recurr(struct PathTree *tree, const char *path, const char *original_pathstr, struct TVFSItem *item, unsigned long index)
{
  const char *pos = strstr(path, "/");
  char *first_part;
  char *last_part;
  if (pos) {
	  first_part = strndup(path, pos - path);
	  if (strlen(pos + 1) > 0) last_part = strdup(pos + 1);
	  else last_part = NULL;
  } else {
	  first_part = strdup(path);
	  last_part = NULL;
  }
  log("   filelist_tree_add_item_recur: pos = '%s'\n", pos);
  log("   filelist_tree_add_item_recur: first_part = '%s'\n", first_part);
  log("   filelist_tree_add_item_recur: last_part = '%s'\n", last_part);

  if (! last_part)
  {   //  final destination, create new item here
	  log("   filelist_tree_add_item_recur: creating new item here.\n");
	  struct PathTree *t = (struct PathTree*)malloc(sizeof(struct PathTree));
	  memset(t, 0, sizeof(struct PathTree));
	  t->items = NULL;
	  t->data = item;
	  t->index = index;
	  t->node = strdup(path);
	  if (original_pathstr)  t->original_pathstr = strdup(original_pathstr);
	  if (t->data)  t->data->FName = strdup(path);
	  if (t->data)  t->data->FDisplayName = strdup(path);
	  //  create new list of subitems and add new item
	  if (! tree->items) tree->items = g_ptr_array_new();
	  g_ptr_array_add(tree->items, t);
  } else
  {   //  find parent node or create new one if doesn't exist
	  log("   filelist_tree_add_item_recur: node '%s', path '%s'\n", tree->node, path);
	  if (! tree->items) tree->items = g_ptr_array_new();

	  struct PathTree *node = NULL;
	  if (tree->items->len > 0) {
		  unsigned int i;
		  for (i = 0; i < tree->items->len; i++)
		  {
			  struct PathTree* t = (struct PathTree*)g_ptr_array_index(tree->items, i);
			  if (strcmp(t->node, first_part) == 0) {
				  log("  filelist_tree_add_item_recur: found node '%s'\n", t->node);
				  node = t;
				  break;
			  }
		  }
	  }

	  //  create new path holder node
	  if (! node) {
		  log("  filelist_tree_add_item_recur: parent node not found, creating new path holder\n");
		  node = (struct PathTree*)malloc(sizeof(struct PathTree));
		  memset(node, 0, sizeof(struct PathTree));
		  node->items = g_ptr_array_new();
		  node->index = 0;
		  node->node = strdup(first_part);
		  node->original_pathstr = NULL;

		  //  create placeholder data
		  node->data = (struct TVFSItem*)malloc(sizeof(struct TVFSItem));
		  memset(node->data, 0, sizeof(struct TVFSItem));
		  node->data->FName = strdup(node->node);
		  node->data->FDisplayName = strdup(node->node);
		  node->data->ItemType = vDirectory;
		  node->data->iMode = S_IRWXO + S_IRWXG + S_IRWXU;
		  node->data->iUID = geteuid();
		  node->data->iGID = getegid();
		  node->data->m_time = time(NULL);
		  node->data->c_time = node->data->m_time;
		  node->data->a_time = node->data->m_time;

		  g_ptr_array_add(tree->items, node);
	  }

	  //  and recurse one level deeper
	  filelist_tree_add_item_recurr(node, last_part, original_pathstr, item, index);
  }

  free(first_part);
  free(last_part);
}


gboolean filelist_tree_add_item(struct PathTree *tree, const char *path, const char *original_pathstr, struct TVFSItem *item, unsigned long index)
{
  if (! tree) {
	  fprintf(stderr, "filelist_tree_add_item: tree == NULL !\n");
	  return FALSE;
  }
  if (! path) {
	  fprintf(stderr, "filelist_tree_add_item: path == NULL !\n");
	  return FALSE;
  }

  if ((strcmp(path, "/") == 0) || (strcmp(path, ".") == 0) || (strcmp(path, "..") == 0) || (strcmp(path, "./") == 0)) {
	  fprintf(stderr, "filelist_tree_add_item: path '%s' is not a valid path\n", path);
	  return FALSE;
  }

  //  remove leading './'
  if (strstr(path, "./") == path)  path += 2;

  //  remove leading and trailing '/' if present
  char *p;
  if (IS_DIR_SEP(*path)) p = exclude_trailing_path_sep(path + 1);
  else p = exclude_trailing_path_sep(path);
  log("  filelist_tree_add_item: p = '%s'\n", p);

  char *pp;
  pp = canonicalize_filename (p);
  if (! pp)
	  pp = strdup (p);

  struct PathTree* found = filelist_tree_find_node_by_path(tree, pp);

  if (found)
  {   //  replace old data with current ones (record might have been created automatically during path building)
	  log("  filelist_tree_add_item: found old item, replacing data\n");
	  found->index = index;
	  //  free old data
	  if (found->data) free_vfs_item(found->data);
	  found->data = item;
	  if (found->data)  found->data->FName = strdup(found->node);
	  if (found->data)  found->data->FDisplayName = strdup(found->node);
  } else
      //  create new item recursively
	  filelist_tree_add_item_recurr(tree, pp, original_pathstr, item, index);

  free(p);
  free(pp);
  return TRUE;
}

struct PathTree* filelist_tree_get_item_by_index(struct PathTree *tree, unsigned long index)
{
  struct PathTree* t = NULL;
  if ((tree) && (tree->items) && (tree->items->len > index)) {
	  t = (struct PathTree*)g_ptr_array_index(tree->items, index);
  }
  return t;
}


void filelist_tree_resolve_symlinks_recurr(struct PathTree *tree, struct PathTree *root_tree, const char *path)
{
	if (tree) {
		if ((tree->items) && (tree->items->len > 0)) {
			unsigned int i;
			for (i = 0; i < tree->items->len; i++)
			{
				struct PathTree* t = (struct PathTree*)g_ptr_array_index(tree->items, i);
				gchar *new_path;

				if ((t) && (t->data) && (t->data->ItemType == vSymlink) && (t->data->sLinkTo)) {
					log("filelist_tree_resolve_symlinks: found '%s/%s' --> '%s'\n", path, t->node, t->data->sLinkTo);
					gchar *rel = resolve_relative(path, t->data->sLinkTo);
					log("  filelist_tree_resolve_symlinks: relative = '%s'\n", rel);
					if (rel) {
						struct PathTree* link_t = filelist_tree_find_node_by_path(root_tree, rel);
						if ((link_t) && (link_t->data)) {
							/*  WARNING: this is dangerous since we're destroying the symlink type */
	//						t->data->ItemType = link_t->data->ItemType;
							t->data->a_time = link_t->data->a_time;
							t->data->c_time = link_t->data->c_time;
							t->data->m_time = link_t->data->m_time;
							t->data->iGID = link_t->data->iGID;
							t->data->iUID = link_t->data->iUID;
							t->data->iMode = link_t->data->iMode;
							t->data->iSize = link_t->data->iSize;
						}
						g_free(rel);
					}
				}
				if ((strlen(path) == 1) && (IS_DIR_SEP(*path)))
					new_path = g_strconcat("/", t->node, NULL);
				else
					new_path = g_strconcat(path, "/", t->node, NULL);

				filelist_tree_resolve_symlinks_recurr(t, root_tree, new_path);
				g_free(new_path);
			}
		}
	}
}

void filelist_tree_resolve_symlinks(struct PathTree *tree)
{
  log("filelist_tree_resolve_symlinks: begin\n");
  filelist_tree_resolve_symlinks_recurr(tree, tree, "/");
  log("filelist_tree_resolve_symlinks: end\n");
}

