/*   Cataract - Static web photo gallery generator
 *   Copyright (C) 2009 Tomas Bzatek <tbzatek@users.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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>

#include <glib.h>

#include <config.h>

#include "setup.h"
#include "items.h"
#include "gallery-utils.h"
#include "generators.h"
#include "stats.h"



#define DEFAULT_DATA_DIR_MODE     S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH


typedef struct {
  TGallerySetup *setup;
  TAlbum *items;
  GList *job_items;
  TPathInfo *path_info;
  gboolean force_update;
  int total_items;
} TJob;

typedef struct {
  TIndexItem *item;
  gboolean gen_done;
  int index;           /*  processed image index  */
  int real_index;      /*  absolute index in the list  */
} TJobItem;



static void
mirror_files (TGallerySetup *setup, gchar **files, const gchar *src_tree, const gchar *dst_dir, const gchar *label)
{
  gchar **extra;
  gchar *s1, *s2, *s3;
  int processed = 0;

  if (files && g_strv_length (files) > 0) {
    extra = files;
    while (*extra) {
      s1 = g_strstrip (*extra);
      if (strlen (s1) > 0) {
        /*  First create target directory if it doesn't exist  */
        s2 = g_build_filename (dst_dir, s1, NULL);
        s3 = g_path_get_dirname (s2);
        g_free (s2);
        if (g_mkdir_with_parents (s3, DEFAULT_DATA_DIR_MODE)) {
          log_error ("error making target extra directory '%s': %s\n", s3, strerror (errno));
          g_free (s3);
          continue;
        }
        g_free (s3);

        /*  Copy the file  */
        s2 = g_build_filename (src_tree, s1, NULL);
        s3 = g_build_filename (dst_dir, s1, NULL);
        if (! setup->update_mode || needs_update (s2, s3)) {
          if (setup->verbose) {
            if (processed == 0)
              printf ("%s", label);
            printf (" %s", s1);
          }
          copy_file (s2, s3);
          processed++;
        }
        g_free (s2);
        g_free (s3);
      }
      extra++;
    }
    if (setup->verbose && processed > 0)
      printf ("\n");
  }
}




G_LOCK_DEFINE (job_items);
G_LOCK_DEFINE (items_print);

/*  run in a cycle, returns when all completed  */
static gpointer
thread_func (gpointer data)
{
  TIndexItem *item;
  gchar *imgname;
  gchar *s1, *s2, *s3;
  TJob *job = data;
  gboolean updated;
  GList *l;
  TJobItem *job_item;

  do {
    item = NULL;
    job_item = NULL;

    /*  Find unprocessed item  */
    G_LOCK (job_items);
    if (job->job_items != NULL)
      for (l = job->job_items; l != NULL; l = g_list_next (l)) {
    	job_item = l->data;
        if (job_item->gen_done == FALSE) {
          job_item->gen_done = TRUE;
          item = job_item->item;
          break;
        }
      }
    G_UNLOCK (job_items);

    /*  actually do some work  */
    if (item != NULL && job_item != NULL) {
      imgname = g_path_get_basename ((item->path == NULL && item->preview) ? item->preview : item->path);
      updated = generate_image (job->setup, job->items, item, job_item->real_index, job->path_info, ! job->force_update);
      
      if (updated && job->setup->verbose) {
        G_LOCK (items_print);
        g_print ("    [%d/%d] Processing item \"%s\"\n", job_item->index, job->total_items, imgname);
        G_UNLOCK (items_print);
      }

      if (updated && job->items->type == GALLERY_TYPE_ALBUM) {
        s1 = g_build_filename (job->setup->real_templates_dir, job->setup->template_photo, NULL);
        s2 = g_strconcat (imgname, GET_EXT (job->setup->index_file_name), NULL);
        s3 = g_build_filename (job->path_info->dest_dir, s2, NULL);
        write_html_image (job->setup, job->path_info, s1, s3, item, job->items);
        g_free (s1);
        g_free (s2);
        g_free (s3);
      }
      g_free (imgname);
    }

  } while (item != NULL);
  return NULL;
}



/*
 *   build_tree: generate complete gallery tree based on source xml files
 *
 *      src_tree      = source directory of the items
 *      dst_dir       = destination of the generated items
 *      parent_index  = parent album to determine our descent in the tree
 *
 */
gboolean
build_tree (TGallerySetup *setup,
            TPathInfo *path_info,
            TAlbum *parent_index,
            int parent_item_index,
            int jobs)
{
  gchar *idx_file;
  TAlbum *items;
  TIndexItem *item;
  gchar *s1, *s2, *s3;
  gchar *thumb_dir;
  gchar *img_big_dir;
  gchar *img_orig_dir;
  const gchar *template;
  gchar *dst_album_file;
  gboolean res;
  int i;
  TJob *job;
  GError *error;
  GThread *thread;
  GList *thread_list;
  gboolean force_update;
  TJobItem *job_item;
  GList *job_items;
  GList *l;
  TPathInfo *child_path_info;

  printf ("Processing directory \"%s\"\n", path_info->src_dir);
  stats_dirs_inc ();

  /*  Check access permissions  */
  if (access (path_info->src_dir, R_OK)) {
    log_error ("error accessing source directory: %s\n", strerror (errno));
    return FALSE;
  }
  if (access (path_info->dest_dir, R_OK | W_OK | X_OK)) {
    if (g_mkdir_with_parents (path_info->dest_dir, DEFAULT_DATA_DIR_MODE)) {
      log_error ("error creating destination directory: %s\n", strerror (errno));
      return FALSE;
    }
  }

  /*  Check the index file  */
  idx_file = g_build_filename (path_info->src_dir, "index.xml", NULL);
  if (access (idx_file, R_OK)) {
    log_error ("error accessing index file '%s': %s\n", idx_file, strerror (errno));
    g_free (idx_file);
    return FALSE;
  }

  /*  Read the index file and fill items array  */
  items = parse_album_xml (idx_file, path_info);
  if (! items) {
    log_error ("error reading index file '%s'\n", idx_file);
    g_free (idx_file);
    return FALSE;
  }
  items->parent_index = parent_index;
  items->parent_item_index = parent_item_index;

  /*  Check if update is necessary  */
  dst_album_file = g_build_filename (path_info->dest_dir, setup->index_file_name, NULL);
  force_update = (! setup->update_mode || needs_update (idx_file, dst_album_file));
  g_free (idx_file);

  /*  Copy support files  */
  if (! setup->support_files_use_common_root || parent_index == NULL) {
    /*  copy only if we're in root level or old-style is active  */
    mirror_files (setup, setup->template_files, setup->real_templates_dir, path_info->dest_dir, "    Copying template files: ");

    /*  favicon  */
    if (setup->favicon_file && strlen (setup->favicon_file) > 0) {
      s3 = g_path_get_dirname (setup->setup_xml_path);
      s1 = g_build_filename (s3, setup->favicon_file, NULL);
      s2 = g_build_filename (path_info->dest_dir, setup->favicon_file, NULL);
      if (! setup->update_mode || needs_update (s1, s2)) {
        if (setup->verbose)  printf ("    Copying favicon:  %s\n", setup->favicon_file);
        copy_file (s1, s2);
      }
      g_free (s1);
      g_free (s2);
      g_free (s3);
    }
  }

  /*  Prepare target thumbnail directory  */
  thumb_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->dest_dir, setup->thumbnail_dir, NULL);
  if (access (thumb_dir, R_OK | W_OK | X_OK))
    if (g_mkdir_with_parents (thumb_dir, DEFAULT_DATA_DIR_MODE)) {
      log_error ("error making target thumbnail directory: %s\n", strerror (errno));
      g_free (thumb_dir);
      g_free (dst_album_file);
      free_album_data (items);
      return FALSE;
    }
  g_free (thumb_dir);

  /*  Prepare target preview and orig directories  */
  if (items->type == GALLERY_TYPE_ALBUM) {
    res = TRUE;
    img_big_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->dest_dir, setup->img_big_dir, NULL);
    img_orig_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->dest_dir, setup->img_orig_dir, NULL);
    if (access (img_big_dir, R_OK | W_OK | X_OK))
      if (g_mkdir_with_parents (img_big_dir, DEFAULT_DATA_DIR_MODE)) {
        log_error ("error making target preview directory: %s\n", strerror (errno));
        res = FALSE;
      }
    if (access (img_orig_dir, R_OK | W_OK | X_OK))
      if (g_mkdir_with_parents (img_orig_dir, DEFAULT_DATA_DIR_MODE)) {
        log_error ("error making target full size directory: %s\n", strerror (errno));
        res = FALSE;
      }
    g_free (img_big_dir);
    g_free (img_orig_dir);
    if (! res) {
      g_free (dst_album_file);
      free_album_data (items);
      return FALSE;
    }
  }

  /*  Prepare job manager structures  */
  job = g_malloc0 (sizeof (TJob));
  job->items = items;
  job->setup = setup;
  job->path_info = path_info;
  job->force_update = force_update;
  job->total_items = 0;

  /*  Create references to the items for job manager to store actual job state in  */
  job_items = NULL;
  if (items->items->len > 0) {
    for (i = 0; i < items->items->len; i++) {
      item = g_ptr_array_index (items->items, i);
      if (item == NULL) {
        log_error ("run_job: error getting item %d\n", i);
        continue;
      }
      if (item->type == INDEX_ITEM_TYPE_PICTURE) {
        job->total_items++;
        job_item = g_malloc0 (sizeof (TJobItem));
        job_item->item = item;
        job_item->gen_done = FALSE;
        job_item->index = job->total_items; 
        job_item->real_index = i;
        job_items = g_list_append (job_items, job_item);
      }
    }
  }
  job->job_items = job_items;
  
  /*  Generate images + particular html pages  */
#ifdef G_THREADS_ENABLED
  thread_list = NULL;

  for (i = 0; i < jobs; i++) {
    error = NULL;
    thread = g_thread_create (thread_func, job, TRUE, &error);
    if (thread)
      thread_list = g_list_append (thread_list, thread);
    if (error) {
      log_error ("build_tree: error starting new thread: %s\n", error->message);
      g_clear_error (&error);
    }
  }

  /*  wait for threads are finished  */
  for (l = thread_list; l != NULL; l = l->next) {
    g_thread_join (l->data);
  }
  /*  TODO: free threads?  */
  g_list_free (thread_list);

#else  /*  threads are disabled  */
  thread_func (job);
#endif
  g_free (job);

  /*  Cleanup generator objects  */
  g_list_foreach (job_items, (GFunc) g_free, NULL);
  g_list_free (job_items);

  /*  Generate album page  */
  if (force_update) {
    if (items->type == GALLERY_TYPE_INDEX)
      template = setup->template_index;
    else
    if (items->type == GALLERY_TYPE_ALBUM)
      template = setup->template_album;
    else
      /*  default to album  */
      template = setup->template_album;

    if (setup->verbose)  printf ("    Writing %s\n", setup->index_file_name);
    s1 = g_build_filename (setup->real_templates_dir, template, NULL);
    res = write_html_album (setup, path_info, s1, dst_album_file, items);
    g_free (s1);
    if (! res)  {
      log_error ("error generating target index file\n");
      free_album_data (items);
      return FALSE;
    }
  }
  g_free (dst_album_file);


  /*  Recurse to sub-albums (in case of album index)  */
  if (items->type == GALLERY_TYPE_INDEX) {
    if (items->items->len > 0) {
      for (i = 0; i < items->items->len; i++) {
        item = g_ptr_array_index (items->items, i);
        if (item == NULL) {
          log_error ("build_tree: error getting item %d\n", i);
          continue;
        }
        if (item->type == INDEX_ITEM_TYPE_PICTURE) {
          child_path_info = g_malloc0 (sizeof (TPathInfo));
          child_path_info->source_root = g_strdup (path_info->source_root);
          child_path_info->dest_root = g_strdup (path_info->dest_root);
          child_path_info->src_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->src_dir, item->path, NULL);
          child_path_info->dest_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->dest_dir, item->path, NULL);
          child_path_info->album_path = g_build_path (G_DIR_SEPARATOR_S, path_info->album_path, item->path, NULL);
          build_tree (setup, child_path_info, items, i, jobs);
          free_path_info (child_path_info);
        }
      }
    }
  }

  /*  Copy extra files  */
  mirror_files (setup, items->extra_files, path_info->src_dir, path_info->dest_dir, "    Copying extra files: ");

  free_album_data (items);
  return TRUE;
}
