use cryptonum::unsigned::*;
use cryptonum::signed::ModInv;
use digest::Digest;
use dsa::params::*;
use dsa::rfc6979::DSASignature;
use num::BigInt;
use simple_asn1::{ToASN1,ASN1Block,ASN1Class,ASN1EncodeErr};
use std::cmp::min;
use utils::TranslateNums;

pub trait DSAPublicKey<Params,L,N> {
    /// Generate a new public key given the parameters and public value.
    fn new(params: Params, y: L) -> Self;
    /// Verify the given signature against the given message, using the
    /// appropriate hash function.
    fn verify<Hash>(&self, m: &[u8], sig: &DSASignature<N>) -> bool
      where Hash: Digest;
}

pub struct DSAPubKey<Params,L> {
    pub(crate) params: Params,
    pub(crate) y: L
}

macro_rules! pubkey_impls {
    ($ptype: ident, $ltype: ident, $ntype: ident, $dbl: ident, $bdbl: ident) => {
        impl DSAPublicKey<$ptype,$ltype,$ntype> for DSAPubKey<$ptype,$ltype>
        {
            fn new(params: $ptype, y: $ltype) -> DSAPubKey<$ptype,$ltype>
            {
                DSAPubKey{ params, y }
            }

            fn verify<Hash>(&self, m: &[u8], sig: &DSASignature<$ntype>) -> bool
             where Hash: Digest
            {
                if sig.r >= self.params.q {
                    return false;
                }
                if sig.s >= self.params.q {
                    return false;
                }
                // w = (s')^-1 mod q;
                if let Some(w) = sig.s.modinv(&self.params.q) {
                    // z = the leftmost min(N, outlen) bits of Hash(M').
                    let mut digest_bytes = <Hash>::digest(m).to_vec();
                    let len = min(digest_bytes.len(), $ptype::n_size() / 8);
                    digest_bytes.truncate(len);
                    let z = $ntype::from_bytes(&digest_bytes);
                    // u1 = (zw) mod q
                    let qdbl = $dbl::from(&self.params.q);
                    let u1 = $ltype::from( (&z * &w) % &qdbl );
                    // u2 = (rw) mod q
                    let u2 = $ltype::from( (&sig.r * &w) % &qdbl );
                    // v = (((g)^u1(y)^u2) mod p) mod q
                    let v_1 = self.params.g.modexp(&u1, &self.params.p);
                    let v_2 = self.y.modexp(&u2, &self.params.p);
                    let bigp = $bdbl::from(&self.params.p);
                    let v_first_mod = (v_1 * v_2) % bigp;
                    let v = $ltype::from(v_first_mod) % $ltype::from(&self.params.q);
                    // if v = r, then the signature is verified
                    return $ntype::from(v) == sig.r
                }
                
                false
            }
        }

        impl ToASN1 for DSAPubKey<$ptype,$ltype> {
            type Error = ASN1EncodeErr;

            fn to_asn1_class(&self, c: ASN1Class)
                -> Result<Vec<ASN1Block>,ASN1EncodeErr>
            {
                let uinty = self.y.to_num();
                let inty = BigInt::from(uinty);
                let yblock = ASN1Block::Integer(c, 0, inty);
                Ok(vec![yblock])
            }
        }
    };
}

pubkey_impls!(L1024N160, U1024, U192, U384, U2048);
pubkey_impls!(L2048N224, U2048, U256, U512, U4096);
pubkey_impls!(L2048N256, U2048, U256, U512, U4096);
pubkey_impls!(L3072N256, U3072, U256, U512, U6144);