/*
    exDOS - Experimental 32 Bit protected mode scripted operating system
    (C) 2005 Jason Hunt
    
    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    Contact: nulluser@gmail.com

    File: fat12.c
*/

#include "types.h"
#include "mem.h"
#include "stdio.h"
#include "video.h"
#include "floppy.h"
#include "fat12.h"

         
fat12_type *test_fs = NULL;         // Base pointer for dynamically
                                             // Allocated filesystem


/* Read a word from an array */
word get_word(byte *d)
{
    word w = d[0] + (d[1] << 8);
    return(w);
}
/* End of get_word */


/* Read a doubleword from an array */
word get_dword(byte *d)
{
    word dw = d[0] + (d[1] << 8) + (d[2] << 16) + (d[3] << 24);
    return(dw);
}
/* Get dword */


/* Get volume parameters from boot sector */
// Also read the fat into memory 
//
void fat12_read_param( fat12_type *fs)
{
    byte *buffer = (byte *) kmallocn(512, "boot sector");

    puts("Volume information for /dev/fd0\n");

    floppy_read_lba(0, buffer, 512 );  // Read boot sector

    // Decode the bios parameter block
    fs->param.bytes_per_sect =        get_word(buffer + 11);
    fs->param.sect_per_cluster =      buffer[13];
    fs->param.num_reserved =          get_word(buffer + 14);
    fs->param.num_fats =              buffer[16];
    fs->param.max_root =              get_word(buffer + 17);
    fs->param.num_sectors =           get_word(buffer + 19);
    fs->param.sect_per_fat =          get_word(buffer + 22);
    fs->param.sect_per_track =        get_word(buffer + 14);
    fs->param.num_heads =             get_word(buffer + 26);
    fs->param.num_hidden =            get_dword(buffer + 28);

    printw("  Bytes per sector: ", fs->param.bytes_per_sect);
    printb("  Sectors per cluster: ", fs->param.sect_per_cluster);
    printw("  Num reserved: ", fs->param.num_reserved);
    printb("  Num fats: ", fs->param.num_fats);
    printw("  Root entries: ", fs->param.max_root);
    printw("  Num sectors: ", fs->param.num_sectors);
    printw("  Sectors per fat: ", fs->param.sect_per_fat);
    printw("  Sectors per track: ", fs->param.sect_per_track); 
    printw("  Num heads: ", fs->param.num_heads);

    kfree(buffer);
}
/* End of fat12_read_param */


/* Display a block of memory */
void dump( byte *data, word size)
{
    word i;

    for (i = 0; i < size; i++)
    {
        if (data[i] >= 0x20 || data[i] == 0x0a)
            putchar(data[i]);
    }
}
/* End of dump */


/* Display a block of memory */
void dump_hex( byte *data, word size)
{
    word i;

    for (i = 0; i < size; i++)
    {
        vga_put_hex(data[i]);
        putchar(' ');
    }
}
/* End of dump */


/* Convert disk filename to kernel filename */
void make_file_name(char *name, char *ext, char *filename)
{
    int i = 8; // Max number of chars in name

    // Add name
    while(*name != ' ' && i--)*filename++ = to_lower(*name++);

    if (!ext) return; // check for a valid string;

    // Add seperator if there is an extension
    if (ext[0] != ' ')
        *filename++ = '.';

    i = 3;      // Max number of chars in extension

    // Add extention
    while(*ext != ' ' && i--) *filename++ = to_lower(*ext++);

    *filename++ = 0;             // Null terminate
}
/* End of make_file_name */



/* Read the FAT into the filesystem info */
void fat12_read_fat( fat12_type *fs )
{
    byte *cur_sector_ptr = (byte *)fs->fat;   // make pointer to the fat
    word cur_sector = fs->param.num_reserved; // skip over the boot sector

    byte i;

    for (i = 0; i < fs->param.sect_per_fat; i++)
    {
        if (floppy_read_lba(cur_sector, cur_sector_ptr, 512) == FLOPPY_ERROR)
        {
            puts("Unable to load fat\n");
            return;
        }
        cur_sector_ptr += 512;
        cur_sector++;
    }
}


/* Use the fat to determine the next cluster for the file */
word fat12_next_cluster( fat12_type *fs, word cluster )
{
    word fat_index = (cluster >> 1) + cluster; // same as * 3/2

    if (cluster & 0x01) 
        return(get_word((byte*)fs->fat + fat_index) >> 4);  // odd
    else
        return(get_word((byte*)fs->fat + fat_index) & 0xfff);  // even
}
/* End of fat12_next_cluster */


/* Read the root directory */
void fat12_read_root( fat12_type *fs )
{
    // Size of root directory
    word root_size = 32 * fs->param.max_root;

    // Starting lba of root dir
    word root_lba = fs->param.num_fats * fs->param.sect_per_fat
                      + fs->param.num_reserved;

    // Number of sectors to read
    word sectors = root_size / fs->param.bytes_per_sect;

    byte * root_ptr = (byte *) fs->root_dir; // Pointer to root dir

    while (sectors--)
    {
        // this need to be fixed to accomidate the new read param
        if (floppy_read_lba(root_lba++, root_ptr, 512) == FLOPPY_ERROR)
        {
            puts("Floppy: (dump_file) Unable to read root\n");
            return;
        }

        root_ptr += 512;
    }
}
/* End of fat12_read_root */




/* Fat12_dir_test */
void fat12_list_dir( fat12_type *fs, char *param )
{
    if (fs == NULL) 
    { 
        puts("Drive not mounted\n");
        return;
    }

    bool do_long = 0;

    if (!strcmp(param, "-l")) do_long = 1;

    // Make a directry the points to the start of the root_dir
    dir_entry_type *cur_entry = (dir_entry_type*) fs->cur_dir;

    byte cur_filename[13];

    while (cur_entry->name[0])
    {
        // Convert the name and extension to a filename 
        make_file_name(cur_entry->name, cur_entry->ext, cur_filename);

        // Entry valid and not a long filename
        if (cur_entry->name[0] != 0 && (cur_entry->file_attr != 0x0f))
        {
            byte l = strlen(cur_filename);
            byte i;

            if (cur_entry->file_attr & FAT12_SUB_DIRECTORY )
                vga_set_attr(0x01); // Blue on black

            puts(cur_filename);

            vga_set_attr(0x07);                   // Reset back to normal

            for (i = l; i < 20; i++) putchar(' ');  // Line spacing

            if (do_long)
            {
                vga_put_hex_32(cur_entry->file_size);
                putchar(' ');
                vga_put_hex(cur_entry->file_attr);
                putchar('\n');
            }
        }

        cur_entry++;
    }

    putchar('\n');
}
/* End of fat12_dir_test */



/* Returns FAT12_ERROR of the object was not found
    otherwise it returns the index into the current dir */
dir_entry_type* fat12_locate_object(fat12_type *fs, char *obname)
{
    // make a directry the points to the start of the root_dir
    dir_entry_type *cur_dir = (dir_entry_type*) fs->cur_dir;

    byte cur_filename[13]; // 8 + '.' + 3 + NULL

    while(cur_dir->name[0])
    {
        // Convert the name and extension to a filename 
        make_file_name(cur_dir->name, cur_dir->ext, cur_filename);

        if (!strcmp(cur_filename, obname)) 
            return(cur_dir);    // Return the dir index f the file

        cur_dir++;
   }

    return(NULL);
}
/* End of fat12_locate_object */


/* This is passed a directory entry.

It loads either the file or directory listing into memory
after aloocating enough space
*/

byte * fat12_read_file(fat12_type *fs, dir_entry_type *dir_entry)
{
   // we have to traverse the fat chain to determine the number of
   // blocks needed.
   // This is because directorys do not indicate the file size

    word cluster = dir_entry->cluster_start;
    word num_sectors = 0;
 
    while (cluster < 0x0ff8) // Loop until end of file flag
    {
        cluster = fat12_next_cluster(fs, cluster); // get next sector from fat
        num_sectors++;
    }


    byte tmp_filename[13];

    make_file_name(dir_entry->name, dir_entry->ext, tmp_filename);

    // Now we know how much memory we need 
    byte *f = (byte *) kmallocn(num_sectors * fs->param.bytes_per_sect, tmp_filename);
   
    if (!f) // bail if unable to get memory
    {
        puts("Unable to allocate memory to load file\n");
        return(NULL);
    }

    cluster = dir_entry->cluster_start; // Get back tot he start of the cluster

    byte * file_ptr = f; // use to index into the file buffer

    while (cluster < 0x0ff8 && num_sectors--) // Loop until end of file flag
    {
        // Convert the cluster to an LBA
        word lba = (cluster - 2) *  fs->param.sect_per_cluster + fs->data_start;

        // Read the cluster into buffer
        if (floppy_read_lba(lba, file_ptr, 512) == FLOPPY_ERROR)
        {
            prints("Unable to load file ", dir_entry->name);
            kfree(f);
            return(NULL);
        }

        // Consult the fat for the next cluster
        cluster = fat12_next_cluster(fs, cluster);

        file_ptr += fs->param.bytes_per_sect; // point to next sector
    }

    return(f);
}
/* End of fat12_read_file */


/* Dump a file from dis */
void fat12_dump_file(fat12_type *fs, char *filename)
{
    if (filename == NULL) return;
    if (!filename[0]) return;

    if (fs == NULL) 
    { 
        puts("Drive not mounted\n");
        return;
    }

    // Find directory entry for the filename
    dir_entry_type *dir_entry = fat12_locate_object(fs, filename);

    // File not found
    if (dir_entry == NULL)
    {
        puts("File not found\n");
        return;
    }

    byte *f = fat12_read_file(fs, dir_entry); // load the file into memory

    dump(f, dir_entry->file_size);

    kfree(f); // free memory allocated by fat12_read_file

    putchar('\n');
}
/* End offat12_dump_file */




/* Change to a new directory */
void fat12_change_dir( fat12_type *fs, char *param)
{
    if (!param[0]) return; // Null dit

    // Find directory name
    dir_entry_type *dir_entry = fat12_locate_object(fs, param);

    // File not found
    if (dir_entry == NULL)
    {
        puts("Dir not found\n");
        return;
    }

    if(!(dir_entry->file_attr & FAT12_SUB_DIRECTORY)) // Not a directory?
    {
        prints(param, " is not a directory");
        return;
    }

    if (fs->cur_dir != fs->root_dir)   // Don't free the root
        kfree((void*)fs->cur_dir);


    if (dir_entry->cluster_start == 0) // Changeing to root dir?
    {
        fs->cur_dir = fs->root_dir;
        return;
    }

    // Read the directory
    fs->cur_dir = fat12_read_file(fs, dir_entry); // load the file into memory

}
/* end of fat12_change_dir */




/* User level commands */



/* Read and store dvolume information */
void fat12_mount( void )
{
    // allocate space for filesystem data
    test_fs= (fat12_type *) kmallocn(sizeof(fat12_type), "filesystem");

    fat12_read_param(test_fs); // Load and decode data from bios param block

    // Allocate space for root directory
    test_fs->root_dir = (byte *) kmallocn(32 * test_fs->param.max_root, "fs root");

    // Allocate space for fat
    test_fs->fat = (byte *) kmallocn(test_fs->param.sect_per_fat 
                                 * test_fs->param.bytes_per_sect, "fs fat");

    // compute starting LBA of data section
    test_fs->data_start = test_fs->param.num_fats * test_fs->param.sect_per_fat
                       + test_fs->param.num_reserved + 
                       + (32 * test_fs->param.max_root) / test_fs->param.bytes_per_sect;

    fat12_read_fat(test_fs);
    fat12_read_root(test_fs);

    // Setup starting dir
    test_fs->cur_dir = test_fs->root_dir;
}
/* End of fat12_mount */


/* Free all memory for mount and relase drive */
void fat12_unmount( void )
{
    kfree(test_fs->root_dir);  // Free root directory
    kfree(test_fs->fat);       // Free fat
    kfree(test_fs);            // Release filesysem data

    test_fs = NULL;             // Invalidate
}
/* End of fat12_unmount */



/* ls command */
void fat12_ls( char *param)
{
    fat12_list_dir(test_fs, param);
}
/* End of ls */

/* cd command */
void fat12_cd( char *param)
{
    fat12_change_dir(test_fs, param);
}
/* End of cd */


/* cat command */
int fat12_cat(byte *filename)
{
    fat12_dump_file(test_fs, filename);

    return(0);
}
/* End of fat12_read_test */


