/* ScummC
 * Copyright (C) 2004-2006  Alban Bedel
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file scc_fd.c
 * @ingroup utils
 * @brief Read/write file with XOR encryption
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>

#include "scc_fd.h"
#include "scc_util.h"

scc_fd_t* new_scc_fd(char* path,int flags,uint8_t key) {
  int fd;
  scc_fd_t* scc_fd;

  // MinGW needs O_BINARY to get the usual behavior
#ifdef O_BINARY
  flags |= O_BINARY;
#endif

  if(flags & O_CREAT)
    fd = open(path,flags,0644);
  else
    fd = open(path,flags);
  if(fd < 0) return NULL;
  scc_fd = malloc(sizeof(scc_fd_t));
  scc_fd->fd = fd;
  scc_fd->enckey = key;
  scc_fd->filename = strdup(path);

  return scc_fd;
}

int scc_fd_close(scc_fd_t* f) {
  int r = close(f->fd);
  free(f->filename);
  free(f);
  return r;
}

int scc_fd_read(scc_fd_t* f,void *buf, size_t count) {
  if(count <= 0) return 0;
  count = read(f->fd,buf,count);
  if(count <= 0) return count;
  else {
    uint8_t* ptr = ((uint8_t*)buf) + count;
    do {
      ptr--;
      *ptr ^= f->enckey;
    } while(ptr != buf);
  }
  return count;
}

uint8_t* scc_fd_load(scc_fd_t* f,size_t count) {
  uint8_t* ret = malloc(count);
  int r,pos = 0;
  
  while(pos < count) {
    r = scc_fd_read(f,ret+pos,count - pos);
    if(r <= 0) {
      free(ret);
      return NULL;
    }
    pos += r;
  }

  return ret;
}

int scc_fd_dump(scc_fd_t* f,char* path,int size) {
  char buf[2048];
  int r=0,w,dfd = open(path,O_CREAT|O_WRONLY,0644);

  if(dfd < 0) {
    scc_log(LOG_ERR,"Failed to open %s: %s\n",path,strerror(errno));
    return 0;
  }

  while(size > 0) {
    r = scc_fd_read(f,buf,size > 2048 ? 2048 : size);
    if(r < 0) {
      scc_log(LOG_ERR,"Read error: %s\n",strerror(errno));
      close(dfd);
      return 0;
    }
    size -= r;
    while(r > 0) {
      w = write(dfd,buf,r);
      if(w < 0) {
	scc_log(LOG_ERR,"Write error: %s\n",strerror(errno));
	close(dfd);
	return 0;
      }
      if(w < r) memmove(buf,buf+w,r-w);
      r -= w;
    }
  }
  close(dfd);
  return 1;
}

off_t scc_fd_seek(scc_fd_t* f, off_t offset, int whence) {
  return lseek(f->fd,offset,whence);
}

off_t scc_fd_pos(scc_fd_t* f) {
  return lseek(f->fd,0,SEEK_CUR);
}

uint8_t scc_fd_r8(scc_fd_t* f) {
  uint8_t r = 0;
  scc_fd_read(f,&r,1);
  return r;
}

uint16_t scc_fd_r16le(scc_fd_t* f) {
  uint16_t a = scc_fd_r8(f);
  uint16_t b = scc_fd_r8(f);
  return a | (b << 8);
}

uint32_t scc_fd_r32le(scc_fd_t* f) {
  uint32_t a = scc_fd_r16le(f);
  uint32_t b = scc_fd_r16le(f);
  return (b << 16) | a;
}

uint16_t scc_fd_r16be(scc_fd_t* f) {
  uint16_t b = scc_fd_r8(f);
  uint16_t a = scc_fd_r8(f);
  return a | (b << 8);
}

uint32_t scc_fd_r32be(scc_fd_t* f) {
  uint32_t b = scc_fd_r16be(f);
  uint32_t a = scc_fd_r16be(f);
  return (b << 16) | a;
}

int scc_fd_write(scc_fd_t* f,void *buf, size_t count) {
  int w;
  uint8_t tmp[count];
  uint8_t* ptr = buf;

  for(w = 0 ; w < count ; w++)
    tmp[w] = ptr[w] ^ f->enckey;

  return write(f->fd,tmp,count);
}

int scc_fd_w8(scc_fd_t*f,uint8_t a) {
  return scc_fd_write(f,&a,sizeof(a));
}

int scc_fd_w16le(scc_fd_t*f,uint16_t a) {
  scc_fd_w8(f,a & 0xFF);
  return scc_fd_w8(f,(a >> 8) & 0xFF);
}

int scc_fd_w32le(scc_fd_t*f,uint32_t a) {
  scc_fd_w16le(f,a & 0xFFFF);
  return scc_fd_w16le(f,(a >> 16) & 0xFFFF);
}

int scc_fd_w16be(scc_fd_t*f,uint16_t a) {
  scc_fd_w8(f,(a >> 8) & 0xFF);
  return scc_fd_w8(f,a & 0xFF);
}

int scc_fd_w32be(scc_fd_t*f,uint32_t a) {
  scc_fd_w16be(f,(a >> 16) & 0xFFFF);
  return scc_fd_w16be(f,a & 0xFFFF);
}

int scc_fd_vprintf(scc_fd_t*f,const char *fmt, va_list ap) {
    char* txt;
    int ret, n = vasprintf(&txt,fmt,ap);
    if(n <= 0) return 0;
    ret = scc_fd_write(f,txt,n);
    free(txt);
    return ret;
}

int scc_fd_printf(scc_fd_t*f,const char *fmt, ...) {
    va_list ap;
    int n;
    va_start(ap, fmt);
    n = scc_fd_vprintf(f,fmt,ap);
    va_end(ap);
    return n;
}        
