/*
 This file is part of GNUnet.
  (C) 2012, 2013 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.voting;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import org.gnunet.util.AbsoluteTime;
import org.gnunet.util.Configuration;
import org.gnunet.util.crypto.EcdsaPrivateKey;
import org.gnunet.util.crypto.EcdsaPublicKey;
import org.gnunet.util.crypto.EcdsaSignature;

/**
 * Group certificate.  Attests to the fact that a voter (identified
 * by a ECDSA public key) belongs to a group (given as string identifier) until a
 * the an expiration date (given as timestamp).
 */
public class GroupCert {
    private String group;
    private EcdsaPublicKey member;
    private EcdsaPublicKey signerPublicKey;
    private AbsoluteTime expiration;
    private EcdsaSignature signature;

    private GroupCert() {
        // do nothing, just make constructor private
    }
    public static GroupCert create(EcdsaPublicKey member, String group, EcdsaPrivateKey signerPrivateKey,
                                   EcdsaPublicKey signerPublicKey, AbsoluteTime expiration) {
        Preconditions.checkNotNull(member);
        Preconditions.checkNotNull(group);
        Preconditions.checkNotNull(signerPrivateKey);
        Preconditions.checkNotNull(signerPublicKey);
        Preconditions.checkNotNull(expiration);
        GroupCert groupCert = new GroupCert();
        groupCert.member = member;
        groupCert.expiration = expiration;
        groupCert.group = group;
        groupCert.signerPublicKey = signerPublicKey;
        String signData = "";
        signData += member.toString();
        signData += expiration.getSeconds();
        signData += group;
        signData += signerPublicKey;
        groupCert.signature = signerPrivateKey.sign(signData.getBytes(), 0);
        return groupCert;

    }

    public static GroupCert fromBallotConfig(Ballot ballot, Configuration cfg) {
        GroupCert groupCert = new GroupCert();
        Optional<String> optGroupSig = cfg.getValueString("vote", "GROUP_SIG");
        Optional<Long> optGroupExp = cfg.getValueNumber("vote", "GROUP_EXPIRATION");
        groupCert.member = ballot.voterPub;
        groupCert.group = ballot.group;
        if (!optGroupSig.isPresent()) {
            throw new InvalidBallotException("missing group cert signature in ballot");
        }
        groupCert.signature = EcdsaSignature.fromString(optGroupSig.get());
        if (groupCert.signature == null) {
            throw new InvalidBallotException("invalid group cert signature in ballot");
        }
        if (!optGroupExp.isPresent()) {
            throw new InvalidBallotException("missing or invalid group cert expiration in ballot");
        }
        groupCert.expiration = AbsoluteTime.fromSeconds(optGroupExp.get());
        return groupCert;
    }

    public static GroupCert fromGroupCertConfig(Configuration cfg) {
        GroupCert groupCert = new GroupCert();
        Optional<String> optMember = cfg.getValueString("groupcert", "MEMBER");
        Optional<String> optCa = cfg.getValueString("groupcert", "CA");
        Optional<String> optSig = cfg.getValueString("groupcert", "SIG");
        Optional<Long> optExpiration = cfg.getValueNumber("groupcert", "EXPIRATION");
        Optional<String> optGroup = cfg.getValueString("groupcert", "GROUP");
        if (!optMember.isPresent()) {
            throw new InvalidGroupCertException("missing member key in group cert");
        }
        if (!optCa.isPresent()) {
            throw new InvalidGroupCertException("missing CA in group cert");
        }
        if (!optSig.isPresent()) {
            throw new InvalidGroupCertException("missing signature in group cert");
        }
        if (!optExpiration.isPresent()) {
            throw new InvalidGroupCertException("missing expiration in group cert");
        }
        if (!optGroup.isPresent()) {
            throw new InvalidGroupCertException("missing group identifier in group cert");
        }
        groupCert.group = optGroup.get();
        groupCert.signature = EcdsaSignature.fromString(optSig.get());
        if (null == groupCert.signature) {
            throw new InvalidGroupCertException("invalid signature in group cert");
        }
        groupCert.expiration = AbsoluteTime.fromSeconds(optExpiration.get());
        groupCert.signerPublicKey = EcdsaPublicKey.fromString(optCa.get());
        if (null == groupCert.signerPublicKey) {
            throw new InvalidGroupCertException("invalid CA in group cert");
        }
        groupCert.member = EcdsaPublicKey.fromString(optMember.get());
        if (null == groupCert.member) {
            throw new InvalidGroupCertException("invalid member in group cert");
        }
        return groupCert;
    }

    /**
     * Encode the group certificate in a ballot configuration.
     *
     * @param cfg ballot configuration
     */
    public void writeBallotConfig(Configuration cfg) {
        cfg.setValueString("vote", "GROUP_SIG", signature.toString());
        cfg.setValueNumber("vote", "GROUP_EXPIRATION", expiration.getSeconds());
    }

    /**
     * Encode the group certificate in a group certificate config file.
     *
     * @param cfg group cert config file
     */
    public void writeGroupCertConfig(Configuration cfg) {
        cfg.setValueString("groupcert", "MEMBER", member.toString());
        cfg.setValueString("groupcert", "CA", signerPublicKey.toString());
        cfg.setValueString("groupcert", "SIG", signature.toString());
        cfg.setValueNumber("groupcert", "EXPIRATION", expiration.getSeconds());
        cfg.setValueString("groupcert", "GROUP", group);
    }

    public EcdsaPublicKey getMemberPublicKey() {
        return member;
    }

    public AbsoluteTime getExpiration() {
        return expiration;
    }

    public EcdsaSignature getSignature() {
        return signature;
    }
}
