/*
 *  copy TOS file to CP/M disk
 *
 *  (c) Jens Mller 1993
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <osbind.h>

#define SPACE                  0x20  /* space character                       */
#define CPM_DRV                0     /* CP/M drive is A:                      */
#define CPM_BLOCK_SIZE         2048  /* CP/M block has 2048 bytes             */
#define CPM_DIR_SIZE           128   /* count of directory entries            */
#define CPM_BLK_PER_DIR_ENTRY  8     /* no. allocateable blocks per dir entry */
#define CPM_LEN_STEP           128   /* file length in 128 byte steps         */
#define DIR_ENTRY_FREE         0xE5  /* unused directory entry                */

#define BAT_FREE               0     /* block free        */
#define BAT_USED               1     /* used block        */
#define BAT_OWN                2     /* used for own file */


/*
 * CP/M cluster 0: track 1, side 0, sector 1
 * CP/M directory: cluster 0,1  (4 kByte = 128 entries)
 * CP/M data     : from cluster 2
 */


typedef struct _intel_word_t
{
   unsigned char  low;
   unsigned char  high;
} intel_word_t;


typedef struct _cpm_dir_entry_t
{
   unsigned char  user;          /* user level, 0xE5 unused              */
   char           fname[11];     /* 8 bytes name and 3 bytes extension   */
   unsigned char  entry_no;      /* entry no. of file (start: 0)         */
   char           res1;          /* always NULL                          */
   char           res2;          /* always NULL                          */
   unsigned char  entry_len;     /* length of file area (128 byte steps) */
   intel_word_t   alloc[CPM_BLK_PER_DIR_ENTRY];      /* allocated blocks */
} cpm_dir_entry_t;


cpm_dir_entry_t   cpm_dir[CPM_DIR_SIZE];   /* CP/M directory           */
unsigned char     cpm_boot[1024];          /* boot sector of CP/M disk */

/* table of allocated blocks */
unsigned char     bat[CPM_DIR_SIZE * CPM_BLK_PER_DIR_ENTRY];


   /* --- read a character from stdin --- */

char read_char ()
{
   char buf[80], *p;

   fgets (buf, 80, stdin);
   p = buf;
   while (*p && isspace(*p))
      p++;
   printf ("\n");
   return (*p);
}


   /* --- write bytes to CP/M disk --- */

write_to_cpm (buf, len, user, fname)
   char   *buf;
   long   len;
   char   user;
   char   *fname;
{
   int    i, j, rv, found, block, write_brk;
   int    letter_ok, retry_flag;
   int    entry_no, entry_len;
   int    bytes_per_sec, sec_cnt;
   int    sec_per_trk, side_cnt;
   int    cpm_start_sec, cpm_sec_per_blk;
   int    cpm_blk_cnt, cpm_dir_blk_cnt;
   int    need_blk, need_dir_entries;
   int    blk_free, dir_entries_free;
   long   i_len;
   char   *p, dsk_fname[11];
   char   c;


      /* --- change file name to disk internal format --- */

   for (i = 0; i < 11; i++)
      dsk_fname[i] = SPACE;

   p = fname;
   for (i = 0; (i < 8) && *p && (*p != '.'); i++)
      dsk_fname[i] = toupper (*p++);
   if (*p == '.')
      p++;
   for (i = 8; (i < 11) && *p && (*p != '.'); i++)
      dsk_fname[i] = toupper (*p++);


      /* --- work with CP/M disk --- */

   printf ("please put CP/M disk into drive A: ");
   getch ();
   printf ("\n");


      /* --- read boot sector ignoring media change --- */

   rv = (int) Rwabs (2, cpm_boot, 1, 0, CPM_DRV);
   if (rv != 0)
      fprintf (stderr, "cannot read boot sector\n");
   else
   {
      bytes_per_sec = cpm_boot[12] * 256 + cpm_boot[11];
      sec_cnt       = cpm_boot[20] * 256 + cpm_boot[19];
      sec_per_trk   = cpm_boot[25] * 256 + cpm_boot[24];
      side_cnt      = cpm_boot[27] * 256 + cpm_boot[26];

      /* first sector of CP/M block 0 */
      cpm_start_sec   = sec_per_trk * side_cnt;

      /* sectors per CP/M block */
      cpm_sec_per_blk = CPM_BLOCK_SIZE / bytes_per_sec;

      /* available CP/M blocks on disk */
      cpm_blk_cnt     = (sec_cnt - cpm_start_sec) / cpm_sec_per_blk;

      /* count of CP/M blocks used by dirctory */
      cpm_dir_blk_cnt = sizeof (cpm_dir) / CPM_BLOCK_SIZE;


         /* --- read CP/M directory, do not ignore media change!  --- */

      rv = (int) Rwabs (0, cpm_dir,
                  cpm_dir_blk_cnt * cpm_sec_per_blk,
                  cpm_start_sec, CPM_DRV);
      if (rv != 0)
         fprintf (stderr, "cannot read CP/M directory\n");
      else
      {

            /* --- check if file already exists --- */

         found = 0;
         for (i = 0; (i < CPM_DIR_SIZE) && (found == 0); i++)
            if (!strncmp (cpm_dir[i].fname, dsk_fname, 11) &&
                cpm_dir[i].user == user)
                   found = 1;

         if (found)
         {
            printf ("file already exists! overwrite? (y/n) ");
            c = read_char ();
            if (tolower(c) == 'y')
            {

                  /* --- erase old file --- */

               for (i = 0; i < CPM_DIR_SIZE; i++)
                  if (!strncmp (cpm_dir[i].fname, dsk_fname, 11) &&
                      cpm_dir[i].user == user)
                         cpm_dir[i].user = DIR_ENTRY_FREE;
               found = 0;
            }
         }

         if (found == 0)
         {

               /* --- calculate needed blocks and dir. entries --- */

            need_blk = len / CPM_BLOCK_SIZE;
            if (len % CPM_BLOCK_SIZE)
               need_blk++;
            need_dir_entries = need_blk / CPM_BLK_PER_DIR_ENTRY;
            if (need_blk % CPM_BLK_PER_DIR_ENTRY)
               need_dir_entries++;


               /* --- build table of allocated blocks --- */

            for (i = 0; i < cpm_dir_blk_cnt; i++)
               bat[i] = BAT_USED;
            for (i = cpm_dir_blk_cnt; i < cpm_blk_cnt; i++)
               bat[i] = BAT_FREE;

            for (i = 0; i < CPM_DIR_SIZE; i++)
               if (cpm_dir[i].user != DIR_ENTRY_FREE)
               {
                  for (j = 0; j < CPM_BLK_PER_DIR_ENTRY; j++)
                  {
                     block = cpm_dir[i].alloc[j].high * 256
                               + cpm_dir[i].alloc[j].low;
                     if ((block > 0) && (block < cpm_blk_cnt))
                        bat[block] = BAT_USED;
                  }
               }


               /* --- calculate free disk space --- */

            blk_free = 0;
            for (i = 0; i < cpm_blk_cnt; i++)
               if (bat[i] == BAT_FREE)
                  blk_free++;

            dir_entries_free = 0;
            for (i = 0; i < CPM_DIR_SIZE; i++)
               if (cpm_dir[i].user == DIR_ENTRY_FREE)
                  dir_entries_free++;

            if (blk_free < need_blk)
               fprintf (stderr, "disk full\n");
            else if (dir_entries_free < need_dir_entries)
               fprintf (stderr, "directory full\n");
            else
            {

                  /* --- write file to disk --- */

               write_brk = 0;
               block     = cpm_dir_blk_cnt - 1;
               i_len     = len;
               i         = 0;
               while ((i_len > 0) && (write_brk == 0))
               {

                     /* --- get next free block --- */

                  block++;
                  while ((bat[block] != BAT_FREE) &&
                         (block < cpm_blk_cnt))
                     block++;

                 if (block >= cpm_blk_cnt)
                 {
                    fprintf (stderr, "disk full (bad sectors)\n");
                    write_brk = 1;

                 } else {

                        /* --- write block --- */

                    do
                    {
                       retry_flag = 0;
                       rv = (int) Rwabs (1, &buf[i], cpm_sec_per_blk,
                                   block * cpm_sec_per_blk + cpm_start_sec,
                                   CPM_DRV);

                       if (rv != 0)
                       {
                          printf ("cannot write to disk!\n");
                          do
                          {
                             printf ("Retry Skip Quit? ");
                             c = read_char ();
                             letter_ok = 1;
                             switch (tolower (c))
                             {
                                case 'r': retry_flag = 1;
                                case 's': break;
                                case 'q': write_brk = 1;
                                          break;
                                default:  letter_ok = 0;
                                          break;
                             }
                          } while (letter_ok == 0);
                       }
                    } while (retry_flag != 0);

                    if ((rv == 0) && (write_brk == 0))
                    {
                       bat[block] = BAT_OWN;
                       i     -= CPM_BLOCK_SIZE;
                       i_len -= CPM_BLOCK_SIZE;
                    }

                    /* else: write same data to next free block */

                  }
               }

               if (write_brk == 0)
               {

                     /* --- update directory --- */

                  i_len    = len;
                  block    = cpm_dir_blk_cnt - 1;
                  entry_no = -1;
                  for (i = 0; (i < CPM_DIR_SIZE) && (i_len > 0); i++)
                     if (cpm_dir[i].user == DIR_ENTRY_FREE)
                     {
                        cpm_dir[i].user = user;
                        strncpy (cpm_dir[i].fname, dsk_fname, 11);
                        entry_no++;
                        cpm_dir[i].entry_no = entry_no;
                        cpm_dir[i].res1 = 0;
                        cpm_dir[i].res2 = 0;

                        j = CPM_BLOCK_SIZE * CPM_BLK_PER_DIR_ENTRY;
                        if (i_len > j)
                        {
                           entry_len = j / CPM_LEN_STEP;
                           if (j % CPM_LEN_STEP)
                              entry_len++;
                        } else {
                           entry_len = i_len / CPM_LEN_STEP;
                           if (i_len % CPM_LEN_STEP)
                              entry_len++;
                        }

                        cpm_dir[i].entry_len = entry_len;

                        for (j = 0; j < CPM_BLK_PER_DIR_ENTRY; j++)
                        {

                              /* --- get next own used block --- */

                           block++;
                           while ((bat[block] != BAT_OWN) &&
                                  (block < cpm_blk_cnt))
                              block++;

                           if (block >= cpm_blk_cnt)
                           {
                              cpm_dir[i].alloc[j].low  = 0;
                              cpm_dir[i].alloc[j].high = 0;
                           } else {
                              cpm_dir[i].alloc[j].low  = block % 256;
                              cpm_dir[i].alloc[j].high = block / 256;
                           }
                        }
                        i_len -= CPM_BLOCK_SIZE * CPM_BLK_PER_DIR_ENTRY;
                     }

                     /* --- write updated directory back --- */

                  rv = (int) Rwabs (1, cpm_dir,
                         cpm_dir_blk_cnt * cpm_sec_per_blk,
                         cpm_start_sec, CPM_DRV);
                  if (rv != 0)
                     fprintf (stderr, "cannot write CP/M directory\n");
               }
            }
         }
      }
   }

   printf ("please put TOS disk into drive A: ");
   getch ();
}


main (argc, argv)
   int  argc;
   char **argv;
{
   char         cpm_user;
   char         *tos_fname, *f_buf, *p;
   int          fd;
   long         len;

   if ((argc < 2) || (argc > 3))
   {
      printf ("usage: tos2cpm  file_name  [user_level]\n");
      exit (1);
   }


      /* --- prepare paramaters --- */

   tos_fname = argv[1];

   cpm_user = 0;
   if (argc == 3)
      cpm_user = (char) atoi (argv[2]);
   if ((cpm_user < 0) || (cpm_user > 15))
   {
      fprintf (stderr, "invalid user level '%d'\n", (int) cpm_user);
      exit (1);
   }


      /* --- read TOS file --- */

   fd = open (tos_fname, O_RDONLY);
   if (fd < 0)
   {
      fprintf (stderr, "cannot open file '%s'\n", tos_fname);
      exit (1);
   }

   len = lseek (fd, 0L, SEEK_END);
   lseek (fd, 0L, SEEK_SET);
   if (len < 0)
   {
      fprintf (stderr, "cannot get file length of '%s'\n",
                       tos_fname);
      close (fd);
      exit (1);
   }
   if (len == 0)
   {
      fprintf (stderr, "empty file '%s'\n", tos_fname);
      close (fd);
      exit (1);
   }

   f_buf = lalloc ((long) len + CPM_BLOCK_SIZE - 1);
   if (f_buf == NULL)
   {
      fprintf (stderr, "not enoght memory\n");
      close (fd);
      exit (1);
   }

   if (lread (fd, f_buf, len) < 1)
   {
      fprintf (stderr, "cannot read file '%s'\n", tos_fname);
      close (fd);
      exit (1);
   }
   close (fd);


      /* --- copy to CP/M disk --- */

   p = strrchr (tos_fname, '\\');
   if (p == NULL)
      p = tos_fname;
   else
      p++;

   write_to_cpm (f_buf, len, cpm_user, p);

   exit (0);
}

