/*
 Copyright (C) 2005 Juliusz Chroboczek

 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, 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; see the file COPYING.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA 02110-1301, USA.
*/

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>

#include "gitlib.h"

struct git_file *
git_read_file(const unsigned char *string)
{
    unsigned long length;
    unsigned char *data;
    unsigned char sha1[20];
    char type[20];
    struct git_file *retval;
    int rc;

    rc = get_sha1_hex(string, sha1);
    if(rc != 0) {
        fprintf(stderr, "Incorrect sha1 hash %s\n", string);
        return NULL;
    }

    data = read_sha1_file(sha1, type, &length);
    if(data == NULL)
        return NULL;

    retval = malloc(sizeof(struct git_file));
    retval->data = data;
    retval->type = strdup(type);
    retval->length = length;
    return retval;
}

void
git_file_done(struct git_file *f)
{
    free(f->data);
    free(f->type);
    free(f);
}

unsigned char *
git_head(char *s)
{
    char buf[41];
    int fd, rc;
    fd = open(s, O_RDONLY);
    if(fd < 0) {
        perror("open(head)");
        return NULL;
    }

    rc = read(fd, buf, 41);
    if(rc < 41) {
        perror("read(head)");
        return NULL;
    }
    if(buf[40] != '\n')
        return NULL;

    buf[40] = '\0';

    return strdup(buf);
}

int
git_update_head(char *name, char *head)
{
    int fd, rc;
    char buf[41];

    if(strlen(head) != 40) {
        fprintf(stderr, "Attempt to write an incorrect head.\n");
        return -1;
    }

    /* HEAD must be updated in-place in case it is a symlink. */
    fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0666);
    if(fd < 0) {
        perror("open(head)");
        return -1;
    }

    strcpy(buf, head);
    buf[40] = '\n';
    do {
        rc = write(fd, buf, 41);
    } while(rc < 0 && errno == EINTR);

    if(rc < 0) {
        close(fd);
        perror("write(head)");
        return -1;
    } else if(rc != 41) {
        close(fd);
        fprintf(stderr, "write(head): partial write.\n");
        return -1;
    }

    close(fd);
    return 1;
}

unsigned int
git_default_file_mode(unsigned int treep)
{
    return treep ? S_IFDIR : 0100644;
}

int
git_is_tree(unsigned int mode)
{
    return S_ISDIR(mode);
}

struct git_tree_iterator *
git_tree_begin(unsigned char *data, unsigned long length)
{
    struct git_tree_iterator *iter;

    iter = malloc(sizeof(struct git_tree_iterator));
    if(iter == NULL)
        return NULL;

    iter->data = data;
    iter->length = length;
    iter->offset = 0;
    return iter;
}

struct git_file_info *
git_tree_next(struct git_tree_iterator *iter)
{
    int len, rc, path;
    unsigned char *ppath;
    struct git_file_info *info;

    if(iter->offset >= iter->length)
        return NULL;

    len = strlen(iter->data + iter->offset);
    ppath = strchr(iter->data + iter->offset, ' ');
    path = ppath - (iter->data + iter->offset) + 1;

    if(ppath == NULL || path < 2 || path > 15 || path >= len || 
       iter->length < iter->offset + len + 20) {
        fprintf(stderr, "Corrupt Git tree file.\n");
        return NULL;
    }

    info = malloc(sizeof(struct git_file_info));
    if(info == NULL) {
        fprintf(stderr, "Couldn't malloc");
        return NULL;
    }

    rc = sscanf(iter->data + iter->offset, "%o", &info->mode);
    if(rc != 1) {
        fprintf(stderr, "Corrupt Git tree file -- couldn't parse mode.\n");
        return NULL;
    }

    info->name = malloc(len - path + 1);
    if(info->name == NULL) {
        fprintf(stderr, "Couldn't malloc");
        return NULL;
    }
    memcpy(info->name, iter->data + iter->offset + path, len - path);
    info->name[len-path] = '\0';

    memcpy(info->sha1, iter->data + iter->offset + len + 1, 20);
    iter->offset = iter->offset + len + 1 + 20;

    return info;
}

void
git_file_info_done(struct git_file_info *info)
{
    free(info->name);
    free(info);
}

void
git_tree_done(struct git_tree_iterator *iter)
{
    free(iter);
}

char *
git_parse_time(unsigned long sec)
{
    char buf[100];
    struct tm *tm;
    time_t time;
    size_t len;
    char *ret;

    time = sec;
    tm = gmtime(&time);
    len = strftime(buf, 100, "%Y%m%d%H%M%S", tm);
    ret = malloc(len + 1);
    memcpy(ret, buf, len);
    ret[len] = '\0';
    return ret;
}

#if defined __GLIBC__
#define HAVE_TM_GMTOFF
#ifndef __UCLIBC__
#define HAVE_TIMEGM
#endif
#define HAVE_SETENV
#endif

#ifdef BSD
#define HAVE_TM_GMTOFF
#define HAVE_SETENV
#endif

#ifdef __CYGWIN__
#define HAVE_SETENV
#endif

#define HAVE_TZSET

/* Like mktime(3), but UTC rather than local time */
#if defined(HAVE_TIMEGM)
time_t
mktime_gmt(struct tm *tm)
{
    return timegm(tm);
}
#elif defined(HAVE_TM_GMTOFF)
time_t
mktime_gmt(struct tm *tm)
{
    time_t t;
    struct tm *ltm;

    t = mktime(tm);
    if(t < 0)
        return -1;
    ltm = localtime(&t);
    if(ltm == NULL)
        return -1;
    return t + ltm->tm_gmtoff;
}
#elif defined(HAVE_TZSET)
#ifdef HAVE_SETENV
/* Taken from the Linux timegm(3) man page. */
time_t
mktime_gmt(struct tm *tm)
{
    time_t t;
    char *tz;

    tz = getenv("TZ");
    setenv("TZ", "", 1);
    tzset();
    t = mktime(tm);
    if(tz)
        setenv("TZ", tz, 1);
    else
        unsetenv("TZ");
    tzset();
    return t;
}
#else
#error no mktime_gmt implementation on this platform
#endif
#error no mktime_gmt implementation on this platform
#endif

unsigned long
git_format_time(char *string)
{
    struct tm tm;
    char *rc;

    rc = strptime(string, "%Y%m%d%H%M%S", &tm);
    if(rc == NULL)
        return 0;
    return mktime_gmt(&tm);
}

struct cache_entry *
git_cache_entry(char *name)
{
    int pos, entries;

    if(active_cache == NULL) {
        entries = read_cache();
        if(entries < 0) {
            fprintf(stderr, "Cannot read Git cache");
            return NULL;
        }
    }

    if(active_cache == NULL)
        return NULL;

    while(name[0] == '.' && name[1] == '/')
        name += 2;

    pos = cache_name_pos(name, strlen(name));
    if(pos < 0)
        return NULL;

    return active_cache[pos];
}

unsigned char *
git_cache_entry_sha1(struct cache_entry *entry)
{
    return entry->sha1;
}

unsigned int
git_cache_entry_size(struct cache_entry *entry)
{
    return ntohl(entry->ce_size);
}

unsigned int
git_cache_entry_mtime(struct cache_entry *entry)
{
    return ntohl(entry->ce_mtime.sec);
}

int
git_validate(char *string, char *sha_b, int n)
{
    int rc;
    char sha_a[20];

    rc = get_sha1_hex(string, sha_a);
    if(rc != 0) {
        fprintf(stderr, "Incorrect sha1 hash %s\n", string);
        return -1;
    }
    rc = memcmp(sha_a, sha_b, 20);
    return rc == 0;
}

struct git_file_info *
git_write_file(char *type, char *name, unsigned mode,
               char *contents, unsigned int length)
{
    int rc;
    char sha1[20];
    struct git_file_info *info;

    rc = write_sha1_file(contents, length, type, sha1);

    if(rc < 0) {
        fprintf(stderr, "Couldn't write Git file.\n");
        return NULL;
    }

    info = malloc(sizeof(struct git_file_info));
    if(info == NULL)
        return NULL;

    info->mode = mode;
    info->name = strdup(name);
    memcpy(info->sha1, sha1, 20);
    return info;
}

struct git_write_iterator *
git_write_tree_begin()
{
    struct git_write_iterator *iter;

    iter = malloc(sizeof(struct git_write_iterator));
    if(iter == NULL)
        return NULL;

    iter->count = 0;
    iter->size = 2048;
    iter->elts = malloc(iter->size * sizeof(struct git_tree_element));
    if(iter->elts == NULL) {
        free(iter);
        return NULL;
    }

    return iter;
}

int
git_write_tree_next(struct git_write_iterator *iter,
                    char *name, unsigned mode, char *sha1_s)
{
    struct git_file_info *info;
    int rc;

    if(iter->count >= iter->size) {
        fprintf(stderr, "Sorry, only %d files in a Git tree.\n", iter->size);
        return -1;
    }

    iter->elts[iter->count].name = strdup(name);
    iter->elts[iter->count].mode = mode;
    rc = get_sha1_hex(sha1_s, iter->elts[iter->count].sha1);
    if(rc < 0) {
        fprintf(stderr, "Couldn't parse SHA1 string.\n");
        return -1;
    }
    iter->count++;
    return 0;
}

struct git_file_info *
git_write_tree_done(struct git_write_iterator *iter, char *name, unsigned mode)
{
    struct git_file_info *info;
    char *buffer, sha1[20];
    int i, j, k, l;

    l = 0;
    for(i = 0; i < iter->count; i++)
        l += strlen(iter->elts[i].name) + 20 + 20;
    l += 200;

    buffer = malloc(l);
    if(buffer == NULL)
        return NULL;

    j = 0;
    for(i = 0; i < iter->count; i++) {
        j += sprintf(buffer + j, "%o %s",
                     iter->elts[i].mode,
                     iter->elts[i].name);
        j++;
        memcpy(buffer + j, iter->elts[i].sha1, 20);
        j += 20;
    }

    write_sha1_file(buffer, j, "tree", sha1);
    free(buffer);

    for(i = 0; i < iter->count; i++)
        free(iter->elts[i].name);
    free(iter->elts);
    free(iter);

    info = malloc(sizeof(struct git_file_info));
    if(info == NULL)
        return NULL;

    info->mode = mode;
    info->name = strdup(name);
    memcpy(info->sha1, sha1, 20);

    return info;
}
