/* 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: /home/doj/code/cbmfs/main.cpp,v 1.5 2010-02-03 05:46:30 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 <string>
#include <map>

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

CBMdisk *cbmdisk = NULL;
static BYTE *disk_image = NULL;
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"
	 "         --di <num>    set directory interleave to <num>\n"
	 "         --fi <num>    set file interleave to <num>\n"
	 );
  exit(1);
}

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

  delete cbmdisk;
  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 printbam=0, printdir=0, printsec=0, speeddos=0, dolphindos=0, prologicdos=0;
  int dir_interleave = -1;
  int file_interleave = -1;

  /// map of image type to file size.
  /// key: image type
  /// val: file size
  std::map<std::string,unsigned> imageSize;
  imageSize["d64"] = 174848;
  imageSize["d71"] = 351062;
  imageSize["d80"] = 533248;
  imageSize["d81"] = 819200;
  imageSize["d82"] = 1066496;

  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); \
  }

  /// check and parse an integer command line argument.
  /// @param x command line argument.
  /// @param var integer variable.
#define CHECK_ARG_I(x,var)		   \
else if(!strcmp(argv[i], "--" #x) && i+1 < argc) \
  { \
    var = atoi(argv[i+1]); \
    MOVE_ARGS(i); \
    MOVE_ARGS(i); \
  }

  for(int 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)
    CHECK_ARG_I(di, dir_interleave)
    CHECK_ARG_I(fi, file_interleave)
    else
      ++i;
    }

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

  /* get file size */
  struct stat buf;
  int open_mode = O_RDWR;
  if(stat(filename, &buf) < 0)
    {
      const int err = errno;
      if (err == ENOENT)
	{
	  std::string fn = filename;
	  const auto pos = fn.rfind('.');
	  if (pos != std::string::npos)
	    {
	      fn.erase(0, pos+1);
	      auto it = imageSize.find(fn);
	      if (it != imageSize.end())
		{
		  open_mode |= O_CREAT;
		  disk_image_size = it->second;
		  goto do_open;
		}
	    }
	}
      printf("could not stat %s : %s\n", filename, strerror(err));
      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 */
 do_open:
  fd=open(filename, open_mode, 0644);
  if(fd < 0)
    {
      printf("could not open %s : %s\n", filename, strerror(errno));
      exit(1);
    }

  if (open_mode & O_CREAT)
    {
      printf("creating new image %s\n", filename);
      if (ftruncate(fd, disk_image_size) < 0)
	{
	  printf("could not set %s file size to %i: %s\n", filename, disk_image_size, 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 */
  switch(disk_image_size)
    {
    case 174848:
    case 175531:
      {
	cbmdisk=new D64(disk_image);
      }
      break;

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

    case 351062:
    case 349696:
      {
	cbmdisk=new D71(disk_image);
      }
      break;

    case 533248:
      {
	cbmdisk=new D80(disk_image);
      }
      break;

    case 819200:
    case 822400:
      {
	cbmdisk=new D81(disk_image);
      }
      break;

    case 1066496:
      {
	cbmdisk=new D82(disk_image);
      }
      break;

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

  if (open_mode & O_CREAT)
    {
      printf("formatting %s\n", filename);
      if (cbmdisk->format() < 0)
	{
	  printf("could not format %s\n", filename);
	  exit(1);
	}
    }

  const BYTE *disk_name = cbmdisk->diskName();
  const BYTE *disk_id = cbmdisk->diskId();
  if(disk_name && disk_id)
    {
      printf("diskname: ");
      for(unsigned i=0; i<16; ++i)
	{
	  char c = disk_name[i];
	  if (c == 0xA0)
	    {
	      c = ' ';
	    }
	  putchar(c);
	}
      printf(",%c%c\n", disk_id[0], disk_id[1]);
    }

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

  if (dir_interleave > 0)
    {
      cbmdisk->dirInterleave = dir_interleave;
    }
  if (file_interleave > 0)
    {
      cbmdisk->fileInterleave = file_interleave;
    }

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