/*
 This file is part of GNUnet.
 (C) 2011, 2012 Christian Grothoff (and other contributing authors)

 GNUnet 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, or (at your
 option) any later version.

 GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 */

package org.gnunet.construct.parsers;

import org.gnunet.construct.Message;
import org.gnunet.construct.ReflectUtil;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * Parse an array that takes up all the available space.
 *
 * @author Florian Dold
 */
public class FillParser implements Parser {
    private final Parser elemParser;

    private final Field targetField;

    public FillParser(Parser p, Field field) {
        targetField = field;
        elemParser = p;
    }

    @Override
    public int getSize(final Message src) {
        int size = 0;
        final Object arr = ReflectUtil.justGet(src, targetField);

        if (arr == null) {
            throw new RuntimeException("array not initialized");
        }

        for (int i = 0; i < Array.getLength(arr); ++i) {
            size += elemParser.getSize((Message) Array.get(arr, i));
        }
        return size;
    }

    @Override
    public int parse(ByteBuffer srcBuf, int frameOffset,
                     Message frameObj, final Message dstObj, List<Field> frameSizePath) {

        if (frameSizePath == null) {
            throw new AssertionError("FillParser expects a non-null frameSizePath. Does the message have a @FrameSizePath annotation?");
        }

        final int frameSize = ReflectUtil.justGetInt(dstObj, frameSizePath);
        int remaining = frameOffset + frameSize - srcBuf.position();
        int size = 0;

        Class<?> elemType = targetField.getType().getComponentType();

        ArrayList<Message> list = new ArrayList<Message>(10);

        while (remaining > 0) {
            @SuppressWarnings("unchecked")
            Message next = ReflectUtil.justInstantiate((Class<Message>) targetField.getType().getComponentType());
            int s = elemParser.parse(srcBuf, frameOffset, frameObj, next, null);
            size += s;
            remaining -= s;
            list.add(next);
        }

        Object arr = Array.newInstance(elemType, list.size());

        try {
            targetField.set(dstObj, list.toArray((Object[]) arr));
        } catch (IllegalAccessException e) {
            throw new AssertionError("cannot access field");
        }

        return size;
    }

    @Override
    public int write(final ByteBuffer dstBuf, final Message src) {
        int size = 0;
        final Object arr = ReflectUtil.justGet(src, targetField);
        for (int i = 0; i < Array.getLength(arr); ++i) {
            size += elemParser.write(dstBuf, (Message) Array.get(arr, i));
        }
        return size;
    }

    @Override
    public void patch(Message m, int frameSize, List<Field> frameSizePath, Message frameObj) {
        if (frameSizePath == null) {
            throw new AssertionError();
        }
        ReflectUtil.justSetInt(frameObj, frameSizePath, frameSize);

        // todo: patch nested messages
    }

    @Override
    public int getStaticSize() {
        // not known
        return 0;
    }

}
