/*-
 * Copyright (C) 2009 Erik Larsson
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package org.catacombae.hfs;

import java.util.LinkedList;
import org.catacombae.hfs.io.ForkFilter;
import org.catacombae.hfs.types.hfscommon.CommonBTNode;
import org.catacombae.hfs.types.hfscommon.CommonHFSCatalogNodeID.ReservedID;
import org.catacombae.hfs.types.hfscommon.CommonHFSVolumeHeader;
import org.catacombae.io.ReadableRandomAccessStream;
import org.catacombae.hfs.plus.HFSPlusVolume;
import org.catacombae.hfs.types.hfscommon.CommonBTHeaderNode;
import org.catacombae.hfs.types.hfscommon.CommonBTIndexRecord;
import org.catacombae.hfs.types.hfscommon.CommonHFSAttributesIndexNode;
import org.catacombae.hfs.types.hfscommon.CommonHFSAttributesKey;
import org.catacombae.hfs.types.hfscommon.CommonHFSAttributesLeafNode;
import org.catacombae.hfs.types.hfscommon.CommonHFSAttributesLeafRecord;
import org.catacombae.hfs.types.hfscommon.CommonHFSCatalogNodeID;
import org.catacombae.hfs.types.hfsplus.HFSPlusAttributesKey;

/**
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class AttributesFile
    extends BTreeFile<CommonHFSAttributesKey, CommonHFSAttributesLeafRecord>
{
    private final HFSPlusVolume view;

    private class Session extends BTreeFileSession {
        ReadableRandomAccessStream attributesFileStream;

        protected ReadableRandomAccessStream getBTreeStream(
                CommonHFSVolumeHeader header)
        {
            if(!(header instanceof CommonHFSVolumeHeader.HFSPlusImplementation))
            {
                throw new RuntimeException("Illegal CommonHFSVolumeHeader " +
                        "flavour (expected HFSPlusImplementation, got " +
                        header.getClass() + ").");
            }

            if(this.attributesFileStream == null) {
                this.attributesFileStream = getAttributesFileStream(
                        (CommonHFSVolumeHeader.HFSPlusImplementation) header);
            }

            return this.attributesFileStream;
        }
    }

    public AttributesFile(HFSPlusVolume view) {
        super(view);
        this.view = view;
    }

    private ReadableRandomAccessStream getAttributesFileStream(
            CommonHFSVolumeHeader.HFSPlusImplementation header) {

        return new ForkFilter(ForkFilter.ForkType.DATA,
                vol.getCommonHFSCatalogNodeID(ReservedID.ATTRIBUTES_FILE).
                toLong(),
                header.getAttributesFile(),
                vol.extentsOverflowFile,
                view.createFSStream(),
                0,
                header.getAllocationBlockSize(),
                header.getAllocationBlockStart() * view.getPhysicalBlockSize());
    }

    BTreeFileSession openSession() {
        return new Session();
    }

    protected CommonHFSAttributesIndexNode createIndexNode(byte[] nodeData,
            int offset, int nodeSize)
    {
        return CommonHFSAttributesIndexNode.createHFSPlus(nodeData, 0, nodeSize);
    }

    protected CommonHFSAttributesLeafNode createLeafNode(byte[] nodeData,
            int offset, int nodeSize)
    {
        return CommonHFSAttributesLeafNode.createHFSPlus(nodeData, 0, nodeSize);
    }

    public CommonBTHeaderNode getHeaderNode() {
        CommonBTNode firstNode = getNode(0);
        if(firstNode instanceof CommonBTHeaderNode) {
            return (CommonBTHeaderNode) firstNode;
        }
        else {
            throw new RuntimeException("Unexpected node type at catalog node " +
                    "0: " + firstNode.getClass());
        }
    }

    public String[] listAttributeNames(final CommonHFSCatalogNodeID nodeID)
    {
        final LinkedList<String> list = new LinkedList<String>();

        listAttributeNames(nodeID, list);

        return list.toArray(new String[list.size()]);
    }

    public void listAttributeNames(final CommonHFSCatalogNodeID nodeID,
            LinkedList<String> list)
    {
        final LinkedList<CommonHFSAttributesLeafRecord> recordList =
                new LinkedList<CommonHFSAttributesLeafRecord>();

        listAttributeRecords(nodeID, recordList);

        for(CommonHFSAttributesLeafRecord r : recordList) {
            CommonHFSAttributesKey k = r.getKey();
            if(k.getStartBlock() == 0) {
                list.add(new String(k.getAttrName()));
            }
        }
    }

    public CommonHFSAttributesLeafRecord[] listAttributeRecords(
            final CommonHFSCatalogNodeID nodeID)
    {
        final LinkedList<CommonHFSAttributesLeafRecord> list =
                new LinkedList<CommonHFSAttributesLeafRecord>();

        listAttributeRecords(nodeID, list);

        return list.toArray(new CommonHFSAttributesLeafRecord[list.size()]);
    }

    public void listAttributeRecords(final CommonHFSCatalogNodeID nodeID,
            final LinkedList<CommonHFSAttributesLeafRecord> list)
    {
        final CommonBTNode rootNode = getRootNode();
        if(rootNode != null) {
            listAttributeRecords(rootNode, nodeID, list);
        }
    }

    private void listAttributeRecords(final CommonBTNode curNode,
            final CommonHFSCatalogNodeID nodeID,
            final LinkedList<CommonHFSAttributesLeafRecord> list)
    {
        final CommonHFSAttributesKey searchKey =
                CommonHFSAttributesKey.create(new HFSPlusAttributesKey(
                ((CommonHFSCatalogNodeID.HFSPlusImplementation) nodeID).
                getHFSCatalogNodeID(), 0, new char[0]));
        final CommonHFSAttributesKey endKey =
                CommonHFSAttributesKey.create(new HFSPlusAttributesKey(
                ((CommonHFSCatalogNodeID.HFSPlusImplementation)
                nodeID.add(1)).getHFSCatalogNodeID(), 0, new char[0]));

        if(curNode instanceof CommonHFSAttributesLeafNode) {
            CommonHFSAttributesLeafNode leafNode =
                    (CommonHFSAttributesLeafNode) curNode;

            int listSizeBefore = list.size();
            if(!findLEKeys(leafNode, searchKey, endKey, true, list)) {
                while(list.size() > listSizeBefore) {
                    list.removeLast();
                }
            }
        }
        else if(curNode instanceof CommonHFSAttributesIndexNode) {
            CommonHFSAttributesIndexNode indexNode =
                    (CommonHFSAttributesIndexNode) curNode;
            LinkedList<CommonBTIndexRecord<CommonHFSAttributesKey>> recList =
                    new LinkedList<CommonBTIndexRecord<CommonHFSAttributesKey>>
                    ();

            /* Search for all keys in index node between search key (inclusive)
             * and end key (exclusive). */
            findLEKeys(indexNode, searchKey, endKey, false, recList);

            for(CommonBTIndexRecord<CommonHFSAttributesKey> rec : recList) {
                listAttributeRecords(getNode(rec.getIndex()), nodeID, list);
            }
        }
        else {
            throw new RuntimeException("Unexpected node type: " +
                    curNode.getClass());
        }
    }
}
