/*   Cataract - Static web photo gallery generator
 *   Copyright (C) 2008 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/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utime.h>
#include <stats.h>

#include "gallery-utils.h"



#define STRING_ALLOC_SIZE 4096

/*
 *   str_replace: replace substring 'search' with a 'replace' string
 *                - multiple occurrences of the string are replaced
 *                - reallocates the original string
 */
void
str_replace (gchar **dst, const gchar *search, const gchar *replace)
{
  gchar *d;
  gchar *src;
  gchar *found;
  int i;
  int alloc_size;
  int used;


  if (! dst || ! search || strlen (*dst) == 0 || strlen (search) == 0 || strstr (*dst, search) == NULL)
    return;

  /*  let's suppose just one occurrence of replace, realloc if needed  */
  used = strlen (*dst) + 1;
  alloc_size = (((used + strlen (replace)) / STRING_ALLOC_SIZE) + 1) * STRING_ALLOC_SIZE;
  d = g_malloc (alloc_size);

  i = 0;
  src = *dst;
  while (strstr (src, search)) {
    found = strstr (src, search);
    used += strlen (replace) - strlen (search);
    if (used > alloc_size) {
      alloc_size = ((used / STRING_ALLOC_SIZE) + 1) * STRING_ALLOC_SIZE;
      d = g_realloc (d, alloc_size);
    }

    /*  copy the data between search string  */
    if (found > src) {
      memcpy (d + i, src, found - src);
      i += found - src;
    }

    /*  copy replace string instead  */
    if (strlen (replace) > 0) {
      memcpy (d + i, replace, strlen (replace));
      i += strlen (replace);
    }
    src = found + strlen (search);
  }

  /*  copy the rest  */
  if (src) {
    memcpy (d + i, src, strlen (src));
    i += strlen (src);
  }
  d[i] = 0x0;

  /*  return fixed string  */
  g_free (*dst);
  *dst = g_strdup (d);
  g_free (d);
}


/*
 *   copy_file: copy file from src to dst
 */
gboolean
copy_file (const gchar *src, const gchar *dst)
{
  #define BUFFER_SIZE 65536

  FILE *fin;
  FILE *fout;
  void *buffer;
  int size_r;
  struct stat st;
  struct utimbuf ut;
  size_t written;

  fin = fopen (src, "r");
  if (fin == NULL) {
    log_error ("copy_file: error reading file \"%s\": %s\n", src, strerror (errno));
    return FALSE;
  }

  fout = fopen (dst, "w");
  if (fout == NULL) {
    log_error ("copy_file: error writing to file \"%s\": %s\n", dst, strerror (errno));
    fclose (fin);
    return FALSE;
  }

  buffer = g_malloc0 (BUFFER_SIZE);
  size_r = BUFFER_SIZE;

  while (! feof (fin) && size_r == BUFFER_SIZE) {
    size_r = fread (buffer, 1, BUFFER_SIZE, fin);
    written = fwrite (buffer, 1, size_r, fout);
    if (written < size_r) {
      log_error ("copy_file: error writing to file \"%s\": %s\n", dst, strerror (errno));
      break;
    }
  }

  fclose (fout);
  fclose (fin);
  g_free (buffer);

  /*  copy timestamps  */
  memset (&st, 0, sizeof (st));
  if (stat (src, &st) == -1) {
    log_error ("copy_file: cannot stat source file \"%s\": %s\n", src, strerror (errno));
    return TRUE;
  }

  memset (&ut, 0, sizeof (ut));
  ut.actime = st.st_atime;
  ut.modtime = st.st_mtime;
  if (utime (dst, &ut) == -1)
    log_error ("copy_file: cannot set timestamps on target file \"%s\": %s\n", dst, strerror (errno));

  return TRUE;
}


/*
 *   make_string: make string of 'substr' substrings
 *                - returns newly allocated string
 */
gchar *
make_string (const gchar *substr, int count)
{
  int i;
  gchar *s;

  if (count < 0)
    count = 0;

  s = g_malloc0 (strlen (substr) * count + 1);
  for (i = 0; i < count; i++)
    memcpy (s + (strlen (substr) * i), substr, strlen (substr));
  s[strlen (substr) * count] = 0;
  return s;
}


/*
 *   fix_entities: replace all invalid & entities with &amp;
 *                 - returns newly allocated string
 */
void
fix_entities (gchar **str)
{
  gchar *d;
  gchar *src;
  gchar *found;
  gchar *scan;
  int i;
  int alloc_size;
  int used;


  if (! *str || strstr (*str, "&") == NULL)
    return;

  i = 0;
  src = *str;
  used = strlen (src) + 1;
  alloc_size = ((used / STRING_ALLOC_SIZE) + 1) * STRING_ALLOC_SIZE;
  d = g_malloc (alloc_size);

  while (strstr (src, "&")) {
    found = strstr (src, "&");

    /*  copy the data between search string  */
    memcpy (d + i, src, found - src + 1);
    i += found - src + 1;

    /*  scroll to next whitespace  */
    scan = found + 1;
    while (scan && (
          (*scan >= 0x41 && *scan <= 0x5a) || (*scan >= 0x61 && *scan <= 0x7a) ||  /* A-Z, a-z */
          (*scan >= 0x30 && *scan <= 0x39) || (*scan == 0x23)                      /* 0-9, #   */
          ))
      scan++;

    if (scan && (*scan == 0x3b)) {
      /*  this is semicolon, correctly closed entity  */
      /*   -- ignore  */
    }
    else {
      /*  replace with &amp;  */
      used += 4;
      if (used > alloc_size) {
        alloc_size = ((used / STRING_ALLOC_SIZE) + 1) * STRING_ALLOC_SIZE;
        d = g_realloc (d, alloc_size);
      }
      memcpy (d + i, "amp;", 4);
      i += 4;
    }
    src = found + 1;
  }

  /*  copy the rest  */
  if (src) {
    memcpy (d + i, src, strlen (src));
    i += strlen (src);
  }
  d[i] = 0x0;

  /*  return fixed string  */
  g_free (*str);
  *str = g_strdup (d);
  g_free (d);
}


/*
 *   remove_tags: remove all occurences of tags beginning with tag_begin and ending with tag_end
 *                - e.g.  remove_tags (&x, "<!--", "-->") will remove all comments
 *                - returns newly allocated string
 */
void
remove_tags (gchar **str, const gchar *tag_begin, const gchar *tag_end)
{
  gchar *src;
  gchar *found;
  gchar *found2;
  gchar *dest;

  if (! *str || ! tag_begin || ! tag_end || strlen (*str) == 0 || strlen (tag_begin) == 0 || strlen (tag_end) == 0)
    return;

  src = *str;

  while ((found = strstr (src, tag_begin)) != NULL) {
    found2 = strstr (found, tag_end);
    if (found2) {
      found2 += strlen (tag_end);
      dest = g_malloc0 (strlen (src) - (found2 - found) + 1);
      memcpy (dest, src, found - src);
      memcpy (dest + (found - src), found2, strlen (found2) + 1);
      g_free (src);
      src = g_strdup (dest);
      g_free (dest);
    }
    else {
      /*  break in case of malformed tag structure, avoid endless loop  */
      log_error ("remove_tags: malformed tag structure detected, strange things may happen\n");
      break;
    }
  }
  *str = src;
}


/*
 *   count_dir_levels: returns number of path elements
 */
int
count_dir_levels (const gchar *path)
{
  int i;
  int level;

  if (path == NULL || strlen (path) == 0)
    return 0;

  level = 1;
  for (i = strlen (path) - 1; i > 0; i--) {
    if (G_IS_DIR_SEPARATOR (*(path + i)) && i > 0 && i < strlen (path))
      level++;
  }

  return level;
}


/*
 *   extract_file_ext: returns pointer to filename extension
 */
const gchar *
extract_file_ext (const gchar *filename)
{
  g_return_val_if_fail (filename != NULL, NULL);
  g_return_val_if_fail (strlen (filename) > 0, NULL);

  return strrchr (filename, '.') + 1;
}


/*
 *   log_error: prints an error and increments stats
 */
/*  this function can possibly store error in some list and print them all at the end again  */
void
log_error (const gchar *format, ...)
{
  va_list args;

  va_start (args, format);
  vfprintf (stderr, format, args);
  va_end (args);

  stats_errors_inc ();
}


/*
 *   needs_update: returns TRUE if the destionation file needs updating, also when missing
 */
gboolean
needs_update (const gchar *source, const gchar *dest)
{
  struct stat src_stat;
  struct stat dst_stat;

  memset (&src_stat, 0, sizeof (src_stat));
  memset (&dst_stat, 0, sizeof (dst_stat));

  /*  if we can't stat the source file, return FALSE to prevent further errors during update  */
  if (stat (source, &src_stat) == -1) {
    log_error ("needs_update: cannot stat source file \"%s\": %s\n", source, strerror (errno));
    return FALSE;
  }

  /*  if we can't stat the destination file, we need update anyway  */
  if (stat (dest, &dst_stat) == -1)
    return TRUE;
  /*  destination file size should not be zero  */
  if (dst_stat.st_size <= 0)
    return TRUE;

  return (src_stat.st_mtime > dst_stat.st_mtime);
}
