/*
 * Copyright (C) 1993, 1994 by Ralf Baechle (linux@uni-koblenz.de)
 *
 * This file is part of Minix-Handler, an AmigaDOS Filehandler to access
 * Minix filesystems via AmigaDOS.
 *
 * Minix-Handler 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, or (at your option)
 * any later version.
 *
 * Minix-Handler 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.
 */

#include <exec/types.h>

#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <math.h>

#include <proto/exec.h>
#include <proto/dos.h>

#include "td.h"
#include "minix.h"
#include "file.h"
#include "volume.h"
#include "timer.h"
#include "debug.h"

/*
 * static prototypes
 */
static UWORD ZoneNoFromInode(UWORD ino, UWORD BlockNum);
static UWORD TruncDirect(UWORD ino, UWORD BlockNum);
static UWORD TruncIndirect(UWORD ino, UWORD BlockNum);
static UWORD TruncDindirect(UWORD ino, UWORD BlockNum);
static UWORD ExtendDirect(UWORD ino, UWORD BlockNum);
static UWORD ExtendIndirect(UWORD ino, UWORD BlockNum);
static UWORD ExtendDindirect(UWORD ino, UWORD BlockNum);
static UWORD Inode_AddZone(UWORD ino, UWORD BlockNum);

/*
 * Block buffer inode operations
 */
static ULONG BufferInode [BLOCK_SIZE/sizeof(ULONG)];
static ULONG BufferBlock [BLOCK_SIZE/sizeof(ULONG)];
static ULONG BufferBlock2 [BLOCK_SIZE/sizeof(ULONG)];

#define NUMBLOCKS(x) (((x) + (BLOCK_SIZE - 1)) >> BLOCK_SIZE_BITS)

/*
 * Truncate or extend file / dir in 'ino'
 * to a total of Size bytes.
 * New blocks will be cleared with '\0'
 */
VOID SetSize(UWORD ino, ULONG Size)
{
	LONG	i;
	UWORD	b;

	DMSG1("  Old Size: %d blocks.\n", NUMBLOCKS(CVol->Inodes[ino].i_size));
	DMSG1("  New Size: %d blocks.\n", NUMBLOCKS(Size));

	if(NUMBLOCKS(Size) < NUMBLOCKS(CVol->Inodes[ino].i_size)) {
		DMSG("  SetSize: Truncating file.\n");
		for(i=NUMBLOCKS(CVol->Inodes[ino].i_size)-1;i>=(LONG)NUMBLOCKS(Size);i--) {
			if(i < 7 ) {
				b = TruncDirect(ino, i);
                		}
			else if(i < 512 + 7) {
				b = TruncIndirect(ino, i - 7);
				}
			else {
				b = TruncDindirect(ino, i - (512 + 7));
				}
			FreeZone(CVol, b);
			}
		}
	else if(NUMBLOCKS(Size) > NUMBLOCKS(CVol->Inodes[ino].i_size)) {
		if(CVol->Inodes[ino].i_size & BLOCK_SIZE_MASK) {
			/*
			 * Fill up last block with '\0'
			 */
			ReadFromInode(ino, Size >> BLOCK_SIZE_BITS, BufferBlock);
			memset(BufferBlock + (Size & BLOCK_SIZE_MASK), 0, BLOCK_SIZE - (Size & BLOCK_SIZE_MASK));
			WriteToInode(ino, Size >> BLOCK_SIZE_BITS, BufferBlock);
			}
		CVol->Inodes[ino].i_size = Size;
		DMSG("  SetSize: Extending not implemented.\n");
		}
	else {
		DMSG("  SetSize: nothing changed.\n");
		}

	WriteInodes(CVol);
}

static UWORD TruncDirect(UWORD ino, UWORD BlockNum)
{
	UWORD	b;

	b = CVol->Inodes[ino].i_zone[BlockNum];
	CVol->Inodes[ino].i_zone[BlockNum] = 0;

	return b;
}

static UWORD TruncIndirect(UWORD ino, UWORD BlockNum)
{
	UWORD	b1, b2;

	b1 = CVol->Inodes[ino].i_zone[7];
	TD_Read(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);

	b2 = ((UWORD *)BufferBlock)[BlockNum];

	if(BlockNum == 0) {
		FreeZone(CVol, CVol->Inodes[ino].i_zone[7]);
		CVol->Inodes[ino].i_zone[7] = 0;
		}
	else {
		((UWORD *)BufferBlock)[BlockNum] = 0;
		TD_Write(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
		}

	return b2;
}

static UWORD TruncDindirect(UWORD ino, UWORD BlockNum)
{
    UWORD   b1, b2, b3;

    b1 = CVol->Inodes[ino].i_zone[8];
    TD_Read(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);

    b2 = ((UWORD *)BufferBlock)[BlockNum >> 9];
    TD_Read(BufferBlock2, BLOCK_SIZE, b2 << BLOCK_SIZE_BITS);

    b3 = ((UWORD *)BufferBlock2)[BlockNum & 0x1ff];

    /*
     * Free the zone
     */

    /*
     * Free the zone in level #2 table
     */
    if(BlockNum & 0x1ff == 0) {
        FreeZone(CVol, b2 );

        /*
         * Free the zone in level #1 table, if required
         */
        ((UWORD *)BufferBlock)[BlockNum >> 9] = 0;
        TD_Write(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
    }

    /*
     * Free the level #1 table, if required
     */
    if(BlockNum & 0x3fe00 == 0) {
        CVol->Inodes[ino].i_zone[8] = 0;
        }

    /*
     * return the number of the block
     */
    return b3;
}

static UWORD ExtendDirect(UWORD ino, UWORD BlockNum)
{
    UWORD   b;

    if((b = AllocZone(CVol)) == 0) {
        /*
         * Disk full
         */
        return 0;
	}
    CVol->Inodes[ino].i_zone[BlockNum] = b;

    return b;
}

static UWORD ExtendIndirect(UWORD ino, UWORD BlockNum)
{
	UWORD	b1, b2;

	/*
	 * Make shure we have enough space to complete operation
	 */
	if(ZonesFree(CVol) < 2) {
		DMSG("  ExtendIndirect: disk full.\n");
		return 0;
		}

	/*
	 * Read indirection block. If non existing create a new one.
	 */
	if((b1 = CVol->Inodes[ino].i_zone[7]) == 0) {
		if((b1 = AllocZone(CVol)) == 0) {
			/*
			 * Disk full
			 */
			DMSG("  ExtendIndirect: disk full.\n");
			return 0;
			}
		memset(BufferBlock, 0, BLOCK_SIZE);
		TD_Write(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
		CVol->Inodes[ino].i_zone[7] = b1;
		}
	else {
		TD_Read(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
		}

	/*
	 * Now allocate new zone and mark it in indirection block
	 */
	if((b2 = AllocZone(CVol)) == 0) {
		/*
		 * Disk full
		 */
		return 0;
		}
	((UWORD *)BufferBlock)[BlockNum] = b2;

	TD_Write(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);

	return b2;
}

static UWORD ExtendDindirect(UWORD ino, UWORD BlockNum)
{
	UWORD	b1, b2, b3;

	/*
	 * Make shure we have enough space to complete operation
	 */
	if(ZonesFree(CVol) < 3) {
		DMSG("  ExtendIndirect: disk full.\n");
		return 0;
		}

	/*
	 * Read level 1 indirection block. If non existing create a new one.
	 */
	if((b1 = CVol->Inodes[ino].i_zone[8]) == 0) {
		if((b1 = AllocZone(CVol)) == 0) {
			/*
			 * Disk full
			 */
			return 0;
			}
		memset(BufferBlock, 0, BLOCK_SIZE);
		TD_Write(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
		CVol->Inodes[ino].i_zone[8] = b1;
		}
	else {
		TD_Read(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
		}

	/*
	 * Read level 2 indirection block. If non existing create a new one.
	 */
	if((b2 = ((UWORD *)BufferBlock)[BlockNum >> 9]) == 0) {
		if((b2 = AllocZone(CVol)) == 0) {
			/*
			 * Disk full
			 */
			return 0;
			}
		memset(BufferBlock2, 0, BLOCK_SIZE);
		TD_Write(BufferBlock2, BLOCK_SIZE, b2 << BLOCK_SIZE_BITS);
		((UWORD *)BufferBlock)[BlockNum >> 9] = b2;
		TD_Write(BufferBlock, BLOCK_SIZE, b1 << BLOCK_SIZE_BITS);
		}
	else {
		TD_Read(BufferBlock2, BLOCK_SIZE, b2 << BLOCK_SIZE_BITS);
		}

	/*
	 * Now allocate new zone and mark it in indirection block
	 */
	if((b3 = AllocZone(CVol)) == 0) {
		/*
		 * Disk full
		 */
		return 0;
		}
	((UWORD *)BufferBlock2)[BlockNum & 0x1ff] = b3;

	TD_Write(BufferBlock2, BLOCK_SIZE, b2 << BLOCK_SIZE_BITS);

	return b3;
}

/*
 * Find Zone i in inode ino
 */
static UWORD ZoneNoFromInode(UWORD ino, UWORD BlockNum)
{
	UWORD	b;

	if(BlockNum < 7 ) {
		b = CVol->Inodes[ino].i_zone[BlockNum];
                }
	else {
		BlockNum -= 7;
		if(BlockNum < 512 ) {
			b = CVol->Inodes[ino].i_zone[7];
			TD_Read(BufferInode, BLOCK_SIZE, b << BLOCK_SIZE_BITS);
			DMSG1("  ZoneNoFromInode1: reading #%d.\n", b);
			b = ((UWORD *)BufferInode)[BlockNum];
			}
		else {
			BlockNum -= 512;
			b = CVol->Inodes[ino].i_zone[8];
			TD_Read(BufferInode, BLOCK_SIZE, b << BLOCK_SIZE_BITS);
			DMSG1("  ZoneNoFromInode2: reading #%d.\n", b);
			b = ((UWORD *)BufferInode)[BlockNum >> 9];
			TD_Read(BufferInode, BLOCK_SIZE, b << BLOCK_SIZE_BITS);
			DMSG1("  ZoneNoFromInode2: reading #%d.\n", b);
			b = ((UWORD *)BufferInode)[BlockNum & 0x1ff];
			}
		}
	return b;
}

static UWORD Inode_AddZone(UWORD ino, UWORD BlockNum)
{
	UWORD	b;

	if(BlockNum < 7 ) {
		b = ExtendDirect(ino, BlockNum);
                }
	else {
		BlockNum -= 7;
		if(BlockNum < 512 ) {
			b = ExtendIndirect(ino, BlockNum);
			}
		else {
			BlockNum -= 512;
			b = ExtendIndirect(ino, BlockNum);
			}
		}

	if(b) {
		DMSG1("  Inode_AddZone: allocated zone %d.\n", b);
		}
	else {
		DMSG("  Inode_AddZone: Disk full.\n");
		}
	return b;
}

VOID ReadFromInode(UWORD ino, ULONG BlockNum, APTR Buffer)
{
	UWORD	b;

	if (BlockNum >= 7+512+512*512 - 1) {
		DMSG("  ReadFromInode: blockNum too big!\n");
		return;
		}

	b = ZoneNoFromInode(ino, BlockNum );
	if(b == 0) {
		/*
		 * We're reading a hole.
		 * (Oops... is this at all implemented in Minix???)
		 */
		memset(Buffer, 0, BLOCK_SIZE);
		}
	else {
		if(TD_Read(Buffer, BLOCK_SIZE, b << BLOCK_SIZE_BITS)) {
			DMSG1("  Error reading Dirblock %d.\n", b);
			}
		else
			DMSG("  Dirblock read successful.\n");
		TD_Motor(MOTOR_OFF);
		}
	return;
}

BOOL WriteToInode(UWORD ino, ULONG BlockNum, APTR Buffer)
{
	UWORD	b;

	if (BlockNum >= 7+512+512*512 - 1) {
		DMSG("  WriteToInode: blockNum too big!\n");
		return FALSE;
		}

	b = ZoneNoFromInode(ino, BlockNum );
	if(b == 0) {
		/*
		 * We're writing to a hole - allocate new block.
		 */
		b = Inode_AddZone(ino, BlockNum);
		if(b == 0) {
			/*
			 * Disk is full
			 */
			DMSG("  WriteToInode: Disk full.\b");
			return FALSE;
			}
		}

	if(TD_Write(Buffer, BLOCK_SIZE, b << BLOCK_SIZE_BITS))
		DMSG("  Error writing Dirblock.\n");
	else
		DMSG("  Dirblock written successful.\n");

	TD_Motor(MOTOR_OFF);

	return TRUE;
}
