/*
    JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.4

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007-2010 The University of Oxford

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    jpc.sourceforge.net
    or the developer website
    sourceforge.net/projects/jpc/

    Conceived and Developed by:
    Rhys Newman, Ian Preston, Chris Dennis

    End of licence header
*/

package org.jpc.support;

import org.jpc.j2se.Option;

import java.util.logging.*;

/**
 * Hard-drive block device implementation.
 * <p>
 * Subclass of <code>RawBlockDevice</code> with the ability to auto-detect drive
 * geometries from DOS paritition or fallback to guessing from the drive size.
 * @author Chris Dennis
 */
public class HDBlockDevice extends RawBlockDevice
{
    private static final Logger LOGGING = Logger.getLogger(HDBlockDevice.class.getName());

    private static final int PART_END_CHS = 0x5;
    private static final int PART_SIZE = 0xc;
    private static final int PART_1 = 0x1be;
    private static final int PART_MAGIC = 0x1fe;       
    
    private final int cylinders;
    private final int heads;
    private final int sectors;

    /**
     * Constructs an instance backed by the given <code>SeekableIODevice</code>.
     * @param data backing
     */
    public HDBlockDevice(SeekableIODevice data)
    {
        super(data);
        
        int detectedCylinders = 0;
        int detectedHeads = 0;
        int detectedSectors = 0;
       
        byte[] mbr = new byte[512];
        if ((read(0, mbr, 1) >= 0) && (mbr[PART_MAGIC] == (byte) 0x55) && (mbr[PART_MAGIC + 1] == (byte) 0xaa)) {
            for (int i = PART_1; i < PART_MAGIC; i += 0x10) {
                int numberSectors = (mbr[i + PART_SIZE] & 0xff) |
                        ((mbr[i + PART_SIZE + 1] & 0xff) << 8) |
                        ((mbr[i + PART_SIZE + 2] & 0xff) << 16) |
                        ((mbr[i + PART_SIZE + 3] & 0xff) << 24);
                if (numberSectors != 0) {
                    detectedHeads = 1 + (mbr[i + PART_END_CHS] & 0xff);
                    detectedSectors = mbr[i + PART_END_CHS + 1] & 0x3f;
                    if (detectedSectors == 0) continue;
                    detectedCylinders = (int) (this.getTotalSectors() / (detectedHeads * detectedSectors));
                    if (detectedCylinders < 1 || detectedCylinders > 16383) {
                        detectedCylinders = 0;
                        continue;
                    }
                }
            }
        }
        
        if (detectedCylinders == 0) { //no geometry information?
            //We'll use a standard LBA geometry
            detectedCylinders = (int) (this.getTotalSectors() / (16 * 63));
            if (detectedCylinders > 16383)
                detectedCylinders = 16383;
            else if (detectedCylinders < 2)
                detectedCylinders = 2;

            detectedHeads = 16;
            detectedSectors = 63;
            if (Option.printCHS.isSet())
                LOGGING.log(Level.INFO, "no geometry information, guessing CHS {0,number,integer}:{1,number,integer}:{2,number,integer}",
                        new Object[]{Integer.valueOf(detectedCylinders), Integer.valueOf(detectedHeads), Integer.valueOf(detectedSectors)
                        });
        }
        
        cylinders = detectedCylinders;
        heads = detectedHeads;
        sectors = detectedSectors;        
    }

    public boolean isLocked()
    {
        return false;
    }

    public void setLock(boolean locked)
    {
    }

    public int getCylinders()
    {
        return cylinders;
    }

    public int getHeads()
    {
        return heads;
    }

    public int getSectors()
    {
        return sectors;
    }

    public Type getType()
    {
        return Type.HARDDRIVE;
    }
    
    public String toString()
    {
        return "HD: " + super.toString();
    }
}
