/* Copyright (c) 2006 Dirk Jagdmann <doj@cubic.org>

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you
       must not claim that you wrote the original software. If you use
       this software in a product, an acknowledgment in the product
       documentation would be appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and
       must not be misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
       distribution. */

/* $Header: /code/cbmfs/main.cpp,v 1.4 2006/07/21 15:05:58 doj Exp $ */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "d64speed.hpp"
#include "d64dolphin.hpp"
#include "d64prologic.hpp"
#include "d71.hpp"
#include "d81.hpp"
#include "d82.hpp"

CBMdisk *disk;
static BYTE *disk_image;
static int disk_image_size;
static int fd=-1;

uid_t cbmfs_uid;
gid_t cbmfs_gid;

/* help screen */
static void help()
{
  printf("usage: cbmfsmount [options] <diskimage> <mount_point> [<FUSE library options>]\n"
	 "options: --help|-h     this text\n"
	 "         --speeddos    interpret 40 track d64 image as SpeedDOS\n"
	 "         --dolphindos  interpret 40 track d64 image as DolphinDOS\n"
	 "         --prologicdos interpret 40 track d64 image as PrologicDOS\n"
	 "         --printbam    print BAM dump then exit\n"
	 "         --printdir    print directory dump then exit\n"
	 "         --printsec    print sectors then exit\n"
	 );
  exit(1);
}

/* main program */
extern "C" void cbmfs_cleanup()
{
  SYSLOG(LOG_DEBUG, "cbmfs_cleanup()\n");

  if(disk)
    delete disk;
  if(disk_image)
    {
      msync(disk_image, disk_image_size, 0);
      munmap(disk_image, disk_image_size);
    }
  if(fd>=0)
    {
      flock(fd, LOCK_UN);
      close(fd);
    }
}

int main(int argc, char **argv)
{
  int i, printbam=0, printdir=0, printsec=0, speeddos=0, dolphindos=0, prologicdos=0;

  printf("cbmfsmount " CBMFSVERSION "\n");

  /* setup some stuff */
  cbmfs_uid=getuid();
  cbmfs_gid=getgid();

  /* check args */
  if(argc < 3)
    help();

#define MOVE_ARGS(a) \
do { \
  int x; \
  for(x=a; x<argc; ++x) \
    argv[x]=argv[x+1]; \
  --argc; \
} while(0)

#define CHECK_ARG(x) \
else if(!strcmp(argv[i], "--" #x)) \
  { \
    x=1; \
    MOVE_ARGS(i); \
  }

  for(i=1; i<argc;)
    if(!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) help();
  CHECK_ARG(printbam)
    CHECK_ARG(printdir)
    CHECK_ARG(printsec)
    CHECK_ARG(speeddos)
    CHECK_ARG(dolphindos)
    CHECK_ARG(prologicdos)
    else
      ++i;

  const char *filename=argv[1];
  MOVE_ARGS(1);

  /* get file size */
  struct stat buf;
  if(stat(filename, &buf) < 0)
    {
      printf("could not stat %s : %s\n", filename, strerror(errno));
      exit(1);
    }
  if(!S_ISREG(buf.st_mode))
    {
      printf("%s is not a regular file\n", filename);
      exit(1);
    }
  disk_image_size=buf.st_size;

  /* open file */
  fd=open(filename, O_RDWR);
  if(fd < 0)
    {
      printf("could not open %s : %s\n", filename, strerror(errno));
      exit(1);
    }

  if(flock(fd, LOCK_EX|LOCK_NB) < 0)
    {
      printf("could not lock %s : %s\nMaybe the file is already mounted?\n", filename, strerror(errno));
      exit(1);
    }

  disk_image = (BYTE*) mmap(NULL, disk_image_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  if(!disk_image)
    {
      printf("could not memorymap %s : %s\n", filename, strerror(errno));
      exit(1);
    }

  /* determine disk type */
  const BYTE *disk_name=0, *disk_id=0;
  switch(disk_image_size)
    {
    case 174848:
    case 175531:
      {
	const BYTE *BAM=disk_image + 0x16500;
	disk=new D64(disk_image);
	disk_name=BAM + 0x90;
	disk_id=BAM + 0xA2;
      }
      break;

    case 196608:
    case 197376:
      {
	const BYTE *BAM=disk_image + 0x16500;
	const BYTE *DOStype=BAM + 0xA5;
	if(speeddos)
	  {
	    disk=new D64SPEED(disk_image);
	    disk_name=BAM + 0x90;
	    disk_id=BAM + 0xA2;
	  }
	else if(dolphindos)
	  {
	    disk=new D64DOLPHIN(disk_image);
	    disk_name=BAM + 0x90;
	    disk_id=BAM + 0xA2;
	  }
	else if(DOStype[0]=='4' && DOStype[0]=='A')
	  {
	    printf("encountered ProfessionalDOS Image! Please submit the image to the author doj@cubic.org\n");
	    disk=new D64SPEED(disk_image);
	    disk_name=BAM + 0x90;
	    disk_id=BAM + 0xA2;
	  }
	else if(prologicdos || (*(BAM+0xB6)=='2' && *(BAM+0xB7)=='P'))
	  {
	    disk=new D64PROLOGIC(disk_image);
	    disk_name=BAM + 0xA4;
	    disk_id=BAM + 0xB6;
	  }
	else
	  {
	    printf("could not determine 40 track d64 file format. Assuming SpeedDOS.\n");
	    disk=new D64SPEED(disk_image);
	    disk_name=BAM + 0x90;
	    disk_id=BAM + 0xA2;
	  }
      }
      break;

    case 351062:
    case 349696:
      {
	const BYTE *BAM=disk_image + 0x16500;
	disk=new D71(disk_image);
	disk_name=BAM + 0x90;
	disk_id=BAM + 0xA2;
      }
      break;

    case 533248:
      {
	const BYTE *HEADER=disk_image + 0x44E00;
	disk=new D80(disk_image);
	disk_name=HEADER + 6;
	disk_id=HEADER + 0x18;
      }
      break;

    case 819200:
    case 822400:
      {
	const BYTE *HEADER=disk_image + 0x61800;
	disk=new D81(disk_image);
	disk_name=HEADER + 4;
	disk_id=HEADER + 0x16;
      }
      break;

    case 1066496:
      {
	const BYTE *HEADER=disk_image + 0x44E00;
	disk=new D82(disk_image);
	disk_name=HEADER + 6;
	disk_id=HEADER + 0x18;
      }
      break;

    default:
      printf("unknown image size.\n");
      exit(1);
    }

  if(disk_name && disk_id)
    {
      printf("diskname: ");
      for(i=0; i<16; ++i)
	putchar(disk_name[i]);
      printf(",%c%c\n", disk_id[0], disk_id[1]);
    }

  /* special functions */
  if(printbam)
    disk->printbam();
  if(printdir)
    disk->printdir();
  if(printsec)
    disk->printsectors();
  if(printbam || printdir || printsec)
    return 0;

  /* start fuse */
  assert(disk);
  return fuse_main(argc, argv, &cbmfs_ops);
}
