/* A reference to an archive resolvable by an ArchiveResolver. e.g. a Freenet URI.
 *
 *  Copyright (C) 2010, 2011 Darrell Karbott
 *
 *  This library 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.0 of the License, or (at your option) any later version.
 *
 *  This library 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 library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *  Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
 *
 *  This file was developed as component of
 * "fniki" (a wiki implementation running over Freenet).
 */

package wormarc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class ExternalRefs {
    public final static class Reference implements Comparable<Reference> {
        public final int mKind;
        public final String mExternalKey;

        public Reference(int kind, String key) {
            mKind = kind;
            mExternalKey = key;

            if (mExternalKey == null) {
                throw new IllegalArgumentException("Key is null");
            }

            if (mKind < 0 || mKind > 127) {
                throw new IllegalArgumentException("kind out of range [0, 127]");
            }
            if (mExternalKey.length() > 32767) {
                throw new IllegalArgumentException("Key too big.");
            }
        }
        // IMPORTANT: Must be able to sort stably so you get an identical binary rep for the same list.
        public int compareTo(Reference other) {
            if (mKind - other.mKind == 0) {
                // Then by key string.
                return mExternalKey.compareTo(other.mExternalKey);
            }
            // First by kind.
            return mKind - other.mKind;
        }
    }
    public final List<Reference> mRefs;

    public final static int KIND_LOCAL = 1;
    public final static int KIND_FREENET = 2;

    public final static Reference CURRENT_ARCHIVE = new Reference(KIND_LOCAL, "current_archive");
    // Like hg rev -1. i.e. the rev before the first.
    public final static Reference NULL_ARCHIVE = new Reference(KIND_LOCAL, "null_archive");
    public final static ExternalRefs NONE = new ExternalRefs(new ArrayList<Reference>());

    ExternalRefs(List<Reference> refs) {
        Collections.sort(refs);  // To get the same serialed rep.
        mRefs = Collections.unmodifiableList(refs);
        if (mRefs.size() > 127) {
            throw new IllegalArgumentException("Too many refs.");
        }
    }

    public InputStream toBytes() throws IOException {
        ByteArrayOutputStream  buffer = new ByteArrayOutputStream();
        DataOutputStream outputStream = new DataOutputStream(buffer);

        outputStream.writeByte(mRefs.size());
        for (Reference ref : mRefs) {
            outputStream.writeByte(ref.mKind);
            byte[] raw = ref.mExternalKey.getBytes(IOUtil.UTF8);
            outputStream.writeShort(raw.length);
            outputStream.write(raw);
        }
        return new ByteArrayInputStream(buffer.toByteArray());
    }

    public static ExternalRefs fromBytes(InputStream rawBytes) throws IOException {
        DataInputStream inputStream = new DataInputStream(rawBytes);
        int count = inputStream.readByte();
        if (count > 127) {
            throw new IOException("Parse Error: count out of range [0, 127]");
        }

        List<Reference> refs = new ArrayList<Reference>();
        while (count > 0) {
            int kind = inputStream.readByte();
            if (count < 0 || count > 127) {
                throw new IOException("Parse Error: kind out of range [0, 127]");
            }

            int rawLength = inputStream.readShort();
            if (rawLength < 0 || rawLength > 32767) {
                throw new IOException("Parse Error: keyLength out of range [0, 32767]");
            }
            byte[] raw = new byte[rawLength];
            inputStream.readFully(raw);
            String key = new String(raw, IOUtil.UTF8);

            refs.add(new Reference(kind, key));
            count--;
        }
        return new ExternalRefs(refs);
    }

    public String pretty(String labelWithTrailingSpace) {
        StringBuilder buffer = new StringBuilder();
        buffer.append(String.format("--- %sExternalRefs ---\n", labelWithTrailingSpace));
        for (Reference ref: mRefs) {
            buffer.append(String.format("   [%d]:%s\n", ref.mKind, ref.mExternalKey));
        }
        buffer.append("---");
        return buffer.toString();
    }
    public String pretty() { return pretty(""); }


    public static ExternalRefs create(List<String> keys, int kind) {
        List<Reference> refs = new ArrayList<Reference>();
        for (String key : keys) {
            refs.add(new Reference(kind, key));
        }
        return new ExternalRefs(refs);
    }
}