/*
 This file is part of GNUnet.
  (C) 2014 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.gns;


import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import org.gnunet.construct.*;
import org.gnunet.gns.records.RecordData;
import org.gnunet.gns.records.UnknownRecordData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * A GNS record.
 */
public class GnsRecord implements Message {
    private static final Logger logger = LoggerFactory
            .getLogger(GnsRecord.class);

    private static boolean registryInitialized;

    private static BiMap<String,Long> recordTypeMap = HashBiMap.create();
    private static Map<Long,Class> recordClassMap = Maps.newHashMap();

    /**
     * Expiration, either absolute or relative time depending on 'flags'.
     */
    @UInt64
    public long expiration;

    /**
     * Size of 'recordDataBytes'.
     */
    @UInt32
    public long dataSize;

    /**
     * Type ID of this record.
     */
    @UInt32
    public long recordType;

    @UInt32
    public long flags;

    @VariableSizeIntegerArray(lengthField = "dataSize", signed = true, bitSize = 8)
    public byte[] recordDataBytes;

    public static void initializeRegistry() {
        if (registryInitialized)
            return;
        Class<?>[] classes = MessageLoader.getUnionCases(RecordData.class);
        for (Class<?> aClass : classes) {
            String recordTypeString;
            try {
                Field field = aClass.getField("recordTypeString");
                recordTypeString = (String) field.get(null);
            } catch (NoSuchFieldException e) {
                logger.warn("RecordData class {} has no recordTypeString field");
                continue;
            } catch (IllegalAccessException e) {
                logger.warn("RecordData class {} can't access recordTypeString field");
                continue;
            }

            long recordTypeId = MessageLoader.getUnionTag(RecordData.class, (Class<? extends MessageUnion>) aClass);
            recordTypeMap.put(recordTypeString, recordTypeId);
            recordClassMap.put(recordTypeId, aClass);
        }
        registryInitialized = true;
    }

    public RecordData getRecordData() {
        initializeRegistry();
        Class cls = recordClassMap.get(recordType);
        if (null == cls) {
            UnknownRecordData unknownRecordData = new UnknownRecordData();
            unknownRecordData.data = recordDataBytes;
        }
        return (RecordData) Construct.parseAs(recordDataBytes, cls);
    }

    public static GnsRecord createFromString(String typeString, String content) {
        long typeId = getIdFromString(typeString);
        if (typeId < 0)
            return null;
        return createFromString(typeId, content);
    }

    public static GnsRecord createFromString(long typeId, String content) {
        initializeRegistry();
        Class cls = recordClassMap.get(typeId);
        if (null == cls) {
            return null;
        }
        Method mth;
        try {
            mth = cls.getMethod("createFromString", String.class);
        } catch (NoSuchMethodException e) {
            return null;
        }
        try {
            return (GnsRecord) mth.invoke(null, content);
        } catch (IllegalAccessException e) {
            return null;
        } catch (InvocationTargetException e) {
            return null;
        }
    }

    public static String getStringFromId(long typeId) {
        initializeRegistry();
        if (!recordTypeMap.inverse().containsKey(typeId)) {
            return null;
        }
        return recordTypeMap.inverse().get(typeId);
    }

    public static long getIdFromString(String typeString) {
        initializeRegistry();
        if (!recordTypeMap.containsKey(typeString)) {
            return -1;
        }
        return recordTypeMap.get(typeString);
    }
}
