/*
 * File Name: images_scanner.cpp
 */

/*
 * This file is part of uds-plugin-images.
 *
 * uds-plugin-images 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.
 *
 * uds-plugin-images 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/>.
 */

/**
 * Copyright (C) 2008 iRex Technologies B.V.
 * All rights reserved.
 */

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#endif

#include <cassert>
#include <iostream>
#include <algorithm>
#include <glib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#ifndef WIN32
#include <libexif/exif-data.h>
#endif

#include "utils.h"
#include "images_scanner.h"
#include "image_page.h"
#include "log.h"

namespace images
{

/*
 * class ImagesScanner
 */
ImagesScanner::ImagesScanner(void)
:filepath("")
,recursive(false)
,isdir(false)
,dir("")
,active(-1) 
{
    LOGPRINTF("entry");

    get_supported_extnames();
}

ImagesScanner::~ImagesScanner(void)
{
    LOGPRINTF("entry");
}

bool ImagesScanner::is_image(const std::string & filename)
{
    // Only check the extension name to imporve the performance of scanning. 
    bool ret = check_extname(filename);

    LOGPRINTF("%s is image? %d", filename.c_str(), ret);

    return ret;
}

bool ImagesScanner::get_size(const std::string & filename,
                             int * width, int * height)
{
    // Set the default values.
    if (width) { *width = 0; }
    if (height) { *height = 0; }
    
    if (gdk_pixbuf_get_file_info(filename.c_str(), width, height))
    {
        return true;
    }
    return false;
}

void ImagesScanner::get_original_rotation(const std::string & filename, 
                                          PluginRotationDegree & rotation)
{
    LOGPRINTF("entry");

    // Set the default return value.
    rotation = Clockwise_Degrees_0;
#ifndef WIN32
    ExifData * ped = exif_data_new_from_file (filename.c_str());
    if (ped == 0) { return; }

    ExifEntry * pee = exif_data_get_entry(ped, EXIF_TAG_ORIENTATION);
    if (pee == 0) 
    {
        exif_data_unref(ped);
        return; 
    }

    // CAUTION: The character set of the returned string is not defined.
    // It may be UTF-8, latin1, the native encoding of the
    // computer, or the native encoding of the camera.
    const unsigned int maxlen = 1024;
    char val[maxlen] = {0};

    if (exif_entry_get_value (pee, val, maxlen))
    {
        // Conversion table for EXIT orientation to rotation degrees.
        // See also:
        //   http://www.impulseadventure.com/photo/exif-orientation.html
        //   http://sylvana.net/jpegcrop/exif_orientation.html
        const struct
              {
                  const char                    *exif_orientation;
                  const PluginRotationDegree    rotation;
              } exif_conversion_tbl[]
              =
              {
                  { "top - left",     Clockwise_Degrees_0   },
                  { "top - right",    Clockwise_Degrees_0   },
                  { "bottom - right", Clockwise_Degrees_180 },
                  { "bottom - left",  Clockwise_Degrees_180 },
                  { "left - top",     Clockwise_Degrees_270 },
                  { "right - top",    Clockwise_Degrees_270 },
                  { "right - bottom", Clockwise_Degrees_90  },
                  { "left - bottom",  Clockwise_Degrees_90  },
                  { NULL,             Clockwise_Degrees_0   }
              };

        for (int i = 0; exif_conversion_tbl[i].exif_orientation; i++)
        {
            if ( strcmp(val, exif_conversion_tbl[i].exif_orientation) == 0 )
            {
                rotation = exif_conversion_tbl[i].rotation;
                break;
            }
        }
    }
    
    exif_data_unref(ped);
    ped = 0;
#endif
    LOGPRINTF("%d", rotation);
}

int ImagesScanner::scan_images(const std::string & filename,
                            bool to_scan_dir,
                            bool recursive_flag,
                            SortType sort_type,
                            bool sort_ascending,
                            Images & results)
{
    set_filename(filename);
    recursive = recursive_flag;

    if (!to_scan_dir)
    {
        if (g_file_test(filename.c_str(), (GFileTest)G_FILE_TEST_IS_REGULAR)
                && is_image(filename))
        {
            ImagePtr image = new Image;
            if (image)
            { 
                image->path = filename;
                image->width = -1;
                image->height = -1;
                image->is_rotation_calculated = false;
                image->rotation = Clockwise_Degrees_0;
                results.push_back(image);
            }
        }
    }
    else
    {
        // Do scanning.
        scan_dir(dir, results);
        // Do sorting.
        sort_images(results, sort_type, sort_ascending);
    }

    return ((active != -1) ? active : 0);
}

void ImagesScanner::get_supported_extnames(void)
{
    // Get the formats supported by gdk_pixbuf.
    GSList * list = gdk_pixbuf_get_formats();
    for (GSList * ptr = list; ptr; ptr = ptr->next)
    {
        GdkPixbufFormat * format = (GdkPixbufFormat*)ptr->data;
        gchar ** extensions = gdk_pixbuf_format_get_extensions(format);
        if (extensions)
        {
            for (int i = 0; extensions[i] != 0; i++)
            {
                LOGPRINTF("%s", extensions[i]);
                extnames.push_back(extensions[i]);
            }
            g_strfreev(extensions);
        }
    }
    
    if (list)
    {
        g_slist_free(list);
    }
}

bool ImagesScanner::check_extname(const std::string & filename)
{
    bool ret = false;

    char ext[MAX_PATH_LEN];
    if (get_ext_name(filename.c_str(), ext, MAX_PATH_LEN))
    {
        for (unsigned int i = 0; i < extnames.size(); i++)
        {
            if (!g_ascii_strncasecmp(extnames[i].c_str(), 
            ext, MAX_PATH_LEN))
            {
                ret = true;
            }
        }
    }

    LOGPRINTF("%s is image? %d", filename.c_str(), ret);

    return ret;
}

void ImagesScanner::set_filename(const std::string & filename)
{
    filepath = filename;

    isdir = (bool)g_file_test(filename.c_str(),
            (GFileTest)(G_FILE_TEST_IS_DIR));
    if (isdir)
    {
        dir = filename;
    }
    else
    {
        char dirpath[MAX_PATH_LEN] = {0};
        if (get_dir_path(filename.c_str(), dirpath, MAX_PATH_LEN))
        {
            dir = dirpath;
        }
    }
 
    if (!dir.empty())
    {
        append_dir_separator(dir);
    }

    LOGPRINTF("%s, %s", filepath.c_str(), dir.c_str());
}

// If there's no G_DIR_SEPARATOR at the end of dirpath, append it.
void ImagesScanner::append_dir_separator(std::string & dirpath)
{
     if (!(G_DIR_SEPARATOR == dirpath.at(dirpath.length() - 1)))
     {
         dirpath += G_DIR_SEPARATOR;
     }
}

void ImagesScanner::scan_dir(const std::string & dirpath,
                             Images & results)
{
     // The directory to scan is empty, return immediately.
     if (dirpath.empty())
     {
         return;
     }
     
     // The pixbuf doesn't support any formats, no need to scan.
     if (!gdk_pixbuf_get_formats())
     {
         return;
     }
     
     // Do the scanning...
     GDir * p = g_dir_open(dirpath.c_str(), 0, 0);
     if (p)
     {
         const char * filename = g_dir_read_name(p);

         std::string path;
         while (filename)
         {
             // Get the whole filepath
             path.clear();
             path = dirpath;
             path.append(filename);
             
             // Check the path mataches against filter.              
             if (g_file_test(path.c_str(), (GFileTest)G_FILE_TEST_IS_REGULAR)
                 && is_image(path))
             {
                 ImagePtr image = new Image;
                 if (!image) { return; }

                 LOGPRINTF("%s", path.c_str());
                
                 image->path = path;
                 
                 // For performance issue, we don't calculate these fields 
                 // 'width' 'height' 'rotation' until we need them. 
                 image->width = -1;
                 image->height = -1;
                 image->is_rotation_calculated = false;
                 image->rotation = Clockwise_Degrees_0;

                 results.push_back(image);
             }
             else if (recursive
                     && g_file_test(path.c_str(),
                         (GFileTest)G_FILE_TEST_IS_DIR))
             {
                 // Scan recursively.
                 append_dir_separator(path);
                 scan_dir(path, results);
             }
             
             // Get the next 'filename'.
             filename = g_dir_read_name(p);
         }
         g_dir_close(p);
     }
}

void ImagesScanner::sort_images(Images & results,
                                SortType sort_type, 
                                bool sort_ascending)
{
    ImagesIter begin = results.begin();
    ImagesIter end = results.end();
    ImagesIter iter;
#ifndef WIN32  
    // Calculate the sort field before sorting.
    // If sort by filepath, no need to calculate.
    if (sort_type != BY_FILEPATH)
    {
        char tmp[MAX_PATH_LEN] = {0};
        struct stat stat_buf;
        
        for (iter = begin; iter != end; ++iter)
        {
            tmp[0] = '\0';
            switch (sort_type)
            {
                case BY_FILENAME:
                    get_file_name((*iter)->path.c_str(), tmp, MAX_PATH_LEN);
                    break;
                case BY_EXT:
                    get_ext_name((*iter)->path.c_str(), tmp, MAX_PATH_LEN);
                    break;
                case BY_DATE:
                    if ((stat((*iter)->path.c_str(), &stat_buf) == 0)
                     && (S_ISREG(stat_buf.st_mode) != 0))
                    {
                        // What is date?
                        struct tm local_time;
                        localtime_r(&stat_buf.st_mtime, &local_time);
                        strftime(tmp, MAX_PATH_LEN, 
                                "%Y-%m-%dT%H:%M:%S", &local_time);
                    }
                    break;
                case BY_SIZE:
                    if ((stat((*iter)->path.c_str(), &stat_buf) == 0)
                        && (S_ISREG(stat_buf.st_mode)))
                    {
                        snprintf(tmp, MAX_PATH_LEN, 
                                "%ld", stat_buf.st_blocks * 512);
                    }    
                    break;
                default:
                    break;
            }

            (*iter)->sort_field = tmp;
        }
    }

    // Sorting
    switch (sort_type)
    {
        case BY_FILENAME:
            if (sort_ascending) 
            { 
                std::sort(begin, end, less_by_filename); 
            }
            else 
            { 
                std::sort(begin, end, greater_by_filename); 
            }
            break;

        case BY_EXT:
            if (sort_ascending) 
            { 
                std::sort(begin, end, less_by_ext); 
            }
            else 
            { 
                std::sort(begin, end, greater_by_ext); 
            }
            break;

        case BY_DATE:
            if (sort_ascending) 
            { 
                std::sort(begin, end, less_by_date); 
            }
            else 
            { 
                std::sort(begin, end, greater_by_date); 
            }
            break;

        case BY_SIZE:
            if (sort_ascending) 
            { 
                std::sort(begin, end, less_by_size); 
            }
            else 
            { 
                std::sort(begin, end, greater_by_size); 
            }
            break;

        case BY_FILEPATH:
        default:
            if (sort_ascending) 
            { 
                std::sort(begin, end, less_by_filepath); 
            }
            else 
            { 
                std::sort(begin, end, greater_by_filepath); 
            } 
            break;
    }

    // Clear the sort field after sorting.
    if (sort_type != BY_FILEPATH)
    {
        for (iter = begin; iter != end; ++iter)
        {
            (*iter)->sort_field.clear();
        }
    }
#endif
    // Calculate the 'active'.
    int i = 0;
    for (iter = begin; iter != end; ++iter)
    {
        if ((*iter)->path == filepath)
        {
            active = i;
            break;
        }
        i++;
    }
}

#ifndef WIN32
bool ImagesScanner::greater_by_filepath(Image * a, Image * b)
{
    int ret = 0;

    if ((!a->path.empty()) && (!b->path.empty()))
    {
        ret = strcasecmp(a->path.c_str(), b->path.c_str());
    }
    else if ((!a->path.empty()) && (b->path.empty()))
    {
        ret = 1;
    }
    else if ((a->path.empty()) && (!b->path.empty()))
    {
        ret = -1;
    }
    
    return ((ret > 0) ? true : false);
}

bool ImagesScanner::greater_by_filename(Image * a, Image * b)
{
    int ret = 0;
    
    if ((!a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = strcasecmp(a->sort_field.c_str(), b->sort_field.c_str());
    }
    else if ((!a->sort_field.empty()) && (b->sort_field.empty()))
    {
        ret = 1;
    }
    else if ((a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = -1;
    }

    if (!ret) { ret = strcasecmp(a->path.c_str(), b->path.c_str());}
    return ((ret > 0) ? true : false);
}

bool ImagesScanner::greater_by_ext(Image * a, Image * b)
{
    int ret = 0;

    if ((!a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = strcasecmp(a->sort_field.c_str(), b->sort_field.c_str());
    }
    else if ((!a->sort_field.empty()) && (b->sort_field.empty()))
    {
        ret = 1;
    }
    else if ((a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = -1;
    }

    if (!ret) { ret = strcasecmp(a->path.c_str(), b->path.c_str());}
    return ((ret > 0) ? true : false);
}

bool ImagesScanner::greater_by_date(Image * a, Image * b)
{
    int ret = 0;

    if ((!a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        struct tm tm_a, tm_b;
        strptime(a->sort_field.c_str(), "%Y-%m-%dT%H:%M:%S", &tm_a);
        strptime(b->sort_field.c_str(), "%Y-%m-%dT%H:%M:%S", &tm_b);

        time_t time_a = mktime(&tm_a); 
        time_t time_b = mktime(&tm_b);

        double diff = difftime(time_a, time_b);
        ret = (int)diff;
    }
    else if ((!a->sort_field.empty()) && (b->sort_field.empty()))
    {
        ret = 1;
    }
    else if ((a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = -1;
    }
        
    if (!ret) { ret = strcasecmp(a->path.c_str(), b->path.c_str());}
    return ((ret > 0) ? true : false);
}

bool ImagesScanner::greater_by_size(Image * a, Image * b)
{
    int ret = 0;

    if ((!a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = atoi(a->sort_field.c_str()) - atoi(b->sort_field.c_str());
    }
    else if ((!a->sort_field.empty()) && (b->sort_field.empty()))
    {
        ret = 1;
    }
    else if ((a->sort_field.empty()) && (!b->sort_field.empty()))
    {
        ret = -1;
    }
 
    if (!ret) { ret = strcasecmp(a->path.c_str(), b->path.c_str());}
    return ((ret > 0) ? true : false);
}

bool ImagesScanner::less_by_filepath(Image * a, Image * b)
{
    return greater_by_filepath(b, a);
}

bool ImagesScanner::less_by_filename(Image * a, Image * b)
{
    return greater_by_filename(b, a);
}

bool ImagesScanner::less_by_ext(Image * a, Image * b)
{
    return greater_by_ext(b, a);
}

bool ImagesScanner::less_by_date(Image * a, Image * b)
{
    return greater_by_date(b, a);
}

bool ImagesScanner::less_by_size(Image * a, Image * b)
{
    return greater_by_size(b, a);
}
#endif

}; // namespace image


