#!/usr/bin/env python3
# Copyright Supranational LLC
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

import os
import re
import sys
import glob
import subprocess

top = """
using System;
using System.Text;
using System.Numerics;
using System.Runtime.InteropServices;
using size_t = System.UIntPtr;

namespace supranational { public static class blst {

public enum ERROR {
    SUCCESS = 0,
    BAD_ENCODING,
    POINT_NOT_ON_CURVE,
    POINT_NOT_IN_GROUP,
    AGGR_TYPE_MISMATCH,
    VERIFY_FAIL,
    PK_IS_INFINITY,
    BAD_SCALAR,
}

public class Exception : ApplicationException {
    private readonly ERROR code;

    public Exception(ERROR err) { code = err; }
    public override string Message
    {   get
        {   switch(code) {
            case ERROR.BAD_ENCODING:        return "bad encoding";
            case ERROR.POINT_NOT_ON_CURVE:  return "point not on curve";
            case ERROR.POINT_NOT_IN_GROUP:  return "point not in group";
            case ERROR.AGGR_TYPE_MISMATCH:  return "aggregate type mismatch";
            case ERROR.VERIFY_FAIL:         return "verify failure";
            case ERROR.PK_IS_INFINITY:      return "public key is infinity";
            case ERROR.BAD_SCALAR:          return "bad scalar";
            default:                        return null;
            }
        }
    }
}

public enum ByteOrder {
    BigEndian,
    LittleEndian,
}

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_keygen([Out] byte[] key, [In] byte[] IKM, size_t IKM_len,
                                   [In] byte[] info, size_t info_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_keygen_v3([Out] byte[] key, [In] byte[] IKM, size_t IKM_len,
                                      [In] byte[] info, size_t info_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_keygen_v4_5([Out] byte[] key, [In] byte[] IKM, size_t IKM_len,
                                        [In] byte[] salt, size_t salt_len,
                                        [In] byte[] info, size_t info_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_keygen_v5([Out] byte[] key, [In] byte[] IKM, size_t IKM_len,
                                      [In] byte[] salt, size_t salt_len,
                                      [In] byte[] info, size_t info_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_derive_master_eip2333([Out] byte[] key,
                                              [In] byte[] IKM, size_t IKM_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_derive_child_eip2333([Out] byte[] key,
                                             [In] byte[] master, uint child_index);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_scalar_from_bendian([Out] byte[] ret, [In] byte[] key);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_bendian_from_scalar([Out] byte[] ret, [In] byte[] key);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_sk_check([In] byte[] key);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_scalar_from_lendian([Out] byte[] key, [In] byte[] inp);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_lendian_from_scalar([Out] byte[] key, [In] byte[] inp);

public struct SecretKey {
    internal byte[] key;

    //public SecretKey() { key = new byte[32]; }
    public SecretKey(byte[] IKM, string info)
    {   key = new byte[32]; keygen(IKM, info);   }
    public SecretKey(byte[] inp, ByteOrder order=ByteOrder.BigEndian)
    {   key = new byte[32];
        switch(order) {
        case ByteOrder.BigEndian:       from_bendian(inp);  break;
        case ByteOrder.LittleEndian:    from_lendian(inp);  break;
        }
    }

    public void keygen(byte[] IKM, string info="")
    {   if (key == null) key = new byte[32];
        byte[] info_bytes = Encoding.UTF8.GetBytes(info);
        blst_keygen(key, IKM, (size_t)IKM.Length,
                         info_bytes, (size_t)info_bytes.Length);
    }
    public void keygen_v3(byte[] IKM, string info="")
    {   if (key == null) key = new byte[32];
        byte[] info_bytes = Encoding.UTF8.GetBytes(info);
        blst_keygen_v3(key, IKM, (size_t)IKM.Length,
                            info_bytes, (size_t)info_bytes.Length);
    }
    public void keygen_v4_5(byte[] IKM, string salt, string info="")
    {   if (key == null) key = new byte[32];
        byte[] salt_bytes = Encoding.UTF8.GetBytes(salt);
        byte[] info_bytes = Encoding.UTF8.GetBytes(info);
        blst_keygen_v4_5(key, IKM, (size_t)IKM.Length,
                              salt_bytes, (size_t)salt_bytes.Length,
                              info_bytes, (size_t)info_bytes.Length);
    }
    public void keygen_v5(byte[] IKM, byte[] salt, string info="")
    {   if (key == null) key = new byte[32];
        byte[] info_bytes = Encoding.UTF8.GetBytes(info);
        blst_keygen_v5(key, IKM, (size_t)IKM.Length,
                            salt, (size_t)salt.Length,
                            info_bytes, (size_t)info_bytes.Length);
    }
    public void keygen_v5(byte[] IKM, string salt, string info="")
    {   keygen_v5(IKM, Encoding.UTF8.GetBytes(salt), info);   }
    public void derive_master_eip2333(byte[] IKM)
    {   if (key == null) key = new byte[32];
        blst_derive_master_eip2333(key, IKM, (size_t)IKM.Length);
    }
    public SecretKey(SecretKey master, uint child_index)
    {   key = new byte[32];
        blst_derive_child_eip2333(key, master.key, child_index);
    }

    public void from_bendian(byte[] inp)
    {   if (inp.Length != 32)
            throw new Exception(ERROR.BAD_ENCODING);
        if (key == null) key = new byte[32];
        blst_scalar_from_bendian(key, inp);
        if (!blst_sk_check(key))
            throw new Exception(ERROR.BAD_ENCODING);
    }
    public void from_lendian(byte[] inp)
    {   if (inp.Length != 32)
            throw new Exception(ERROR.BAD_ENCODING);
        if (key == null) key = new byte[32];
        blst_scalar_from_lendian(key, inp);
        if (!blst_sk_check(key))
            throw new Exception(ERROR.BAD_ENCODING);
    }

    public byte[] to_bendian()
    {   byte[] ret = new byte[32];
        blst_bendian_from_scalar(ret, key);
        return ret;
    }
    public byte[] to_lendian()
    {   byte[] ret = new byte[32];
        blst_lendian_from_scalar(ret, key);
        return ret;
    }
}

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_scalar_from_be_bytes([Out] byte[] ret, [In] byte[] inp,
                                                               size_t inp_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_scalar_from_le_bytes([Out] byte[] ret, [In] byte[] inp,
                                                               size_t inp_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_sk_add_n_check([Out] byte[] ret, [In] byte[] a,
                                                         [In] byte[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_sk_sub_n_check([Out] byte[] ret, [In] byte[] a,
                                                         [In] byte[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_sk_mul_n_check([Out] byte[] ret, [In] byte[] a,
                                                         [In] byte[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_sk_inverse([Out] byte[] ret, [In] byte[] a);

public struct Scalar {
    internal byte[] val;

    //public Scalar() { val = new byte[32]; }
    public Scalar(byte[] inp, ByteOrder order=ByteOrder.BigEndian)
    {   val = new byte[32];
        switch(order) {
        case ByteOrder.BigEndian:       from_bendian(inp);  break;
        case ByteOrder.LittleEndian:    from_lendian(inp);  break;
        }
    }
    private Scalar(bool _)      { val = new byte[32];               }
    private Scalar(Scalar orig) { val = (byte[])orig.val.Clone();   }

    public Scalar dup()         { return new Scalar(this);          }

    public void from_bendian(byte[] inp)
    {   if (val == null) val = new byte[32];
        blst_scalar_from_be_bytes(val, inp, (size_t)inp.Length);
    }
    public void from_lendian(byte[] inp)
    {   if (val == null) val = new byte[32];
        blst_scalar_from_le_bytes(val, inp, (size_t)inp.Length);
    }

    public byte[] to_bendian()
    {   byte[] ret = new byte[32];
        blst_bendian_from_scalar(ret, val);
        return ret;
    }
    public byte[] to_lendian()
    {   byte[] ret = new byte[32];
        blst_lendian_from_scalar(ret, val);
        return ret;
    }

    public Scalar add(SecretKey a)
    {   if (!blst_sk_add_n_check(val, val, a.key))
            throw new Exception(ERROR.BAD_SCALAR);
        return this;
    }
    public Scalar add(Scalar a)
    {   if (!blst_sk_add_n_check(val, val, a.val))
            throw new Exception(ERROR.BAD_SCALAR);
        return this;
    }
    public Scalar sub(Scalar a)
    {   if (!blst_sk_sub_n_check(val, val, a.val))
            throw new Exception(ERROR.BAD_SCALAR);
        return this;
    }
    public Scalar mul(Scalar a)
    {   if (!blst_sk_mul_n_check(val, val, a.val))
            throw new Exception(ERROR.BAD_SCALAR);
        return this;
    }
    public Scalar inverse()
    {   blst_sk_inverse(val, val); return this;   }

    public static Scalar operator+(Scalar a, Scalar b)
    {   return a.dup().add(b);   }
    public static Scalar operator-(Scalar a, Scalar b)
    {   return a.dup().sub(b);   }
    public static Scalar operator*(Scalar a, Scalar b)
    {   return a.dup().mul(b);   }
    public static Scalar operator/(Scalar a, Scalar b)
    {   return b.dup().inverse().mul(a);   }
}

private const int P1_COMPRESSED_SZ = 384/8;
private const int P2_COMPRESSED_SZ = 2*P1_COMPRESSED_SZ;
"""
middle = """
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern size_t blst_p1_affine_sizeof();

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ERROR blst_p1_deserialize([Out] long[] ret, [In] byte[] inp);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_affine_serialize([Out] byte[] ret, [In] long[] inp);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_affine_compress([Out] byte[] ret, [In] long[] inp);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_to_affine([Out] long[] ret, [In] long[] inp);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_affine_on_curve([In] long[] point);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_affine_in_g1([In] long[] point);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_affine_is_inf([In] long[] point);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_affine_is_equal([In] long[] a, [In] long[] b);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr blst_p1_generator();

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ERROR blst_core_verify_pk_in_g2([In] long[] pk, [In] long[] sig,
                                              bool hash_or_encode,
                                              [In] byte[] msg, size_t msg_len,
                                              [In] byte[] dst, size_t dst_len,
                                              [In] byte[] aug, size_t aug_len);

public struct P1_Affine {
    internal readonly long[] point;

    private static readonly int sz = (int)blst_p1_affine_sizeof()/sizeof(long);

    //public P1_Affine()            { point = new long[sz]; }
    private P1_Affine(bool _)       { point = new long[sz]; }
    private P1_Affine(P1_Affine p)  { point = (long[])p.point.Clone(); }

    public P1_Affine(byte[] inp) : this(true)
    {   int len = inp.Length;
        if (len == 0 || len != ((inp[0]&0x80) == 0x80 ? P1_COMPRESSED_SZ
                                                      : 2*P1_COMPRESSED_SZ))
            throw new Exception(ERROR.BAD_ENCODING);
        ERROR err = blst_p1_deserialize(point, inp);
        if (err != ERROR.SUCCESS)
            throw new Exception(err);
    }
    public P1_Affine(P1 jacobian) : this(true)
    {   blst_p1_to_affine(point, jacobian.point);   }

    public P1_Affine dup()      { return new P1_Affine(this);   }
    public P1 to_jacobian()     { return new P1(this);          }
    public byte[] serialize()
    {   byte[] ret = new byte[2*P1_COMPRESSED_SZ];
        blst_p1_affine_serialize(ret, point);
        return ret;
    }
    public byte[] compress()
    {   byte[] ret = new byte[P1_COMPRESSED_SZ];
        blst_p1_affine_compress(ret, point);
        return ret;
    }

    public bool on_curve()      { return blst_p1_affine_on_curve(point);    }
    public bool in_group()      { return blst_p1_affine_in_g1(point);       }
    public bool is_inf()        { return blst_p1_affine_is_inf(point);      }
    public bool is_equal(P1_Affine p)
    {   return blst_p1_affine_is_equal(point, p.point);   }

    ERROR core_verify(P2_Affine pk, bool hash_or_encode,
                      byte[] msg, string DST = "", byte[] aug = null)
    {   byte[] dst = Encoding.UTF8.GetBytes(DST);
        return blst_core_verify_pk_in_g2(pk.point, point,
                                         hash_or_encode,
                                         msg, (size_t)msg.Length,
                                         dst, (size_t)dst.Length,
                                         aug, (size_t)(aug!=null ? aug.Length : 0));
    }

    public static P1_Affine generator()
    {   var ret = new P1_Affine(true);
        Marshal.Copy(blst_p1_generator(), ret.point, 0, ret.point.Length);
        return ret;
    }
}

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern size_t blst_p1_sizeof();
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_serialize([Out] byte[] ret, [In] long[] inp);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_compress([Out] byte[] ret, [In] long[] inp);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_from_affine([Out] long[] ret, [In] long[] inp);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_on_curve([In] long[] point);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_in_g1([In] long[] point);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_is_inf([In] long[] point);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_p1_is_equal([In] long[] a, [In] long[] b);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_sk_to_pk_in_g1([Out] long[] ret, [In] byte[] SK);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_encode_to_g1([Out] long[] ret, [In] byte[] msg, size_t msg_len,
                                         [In] byte[] dst, size_t dst_len,
                                         [In] byte[] aug, size_t aug_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_hash_to_g1([Out] long[] ret, [In] byte[] msg, size_t msg_len,
                                       [In] byte[] dst, size_t dst_len,
                                       [In] byte[] aug, size_t aug_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_sign_pk_in_g2([Out] long[] ret, [In] long[] hash, [In] byte[] SK);

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_p1_mult([Out] long[] ret, [In] long[] a,
                                    [In] byte[] scalar, size_t nbits);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_cneg([Out] long[] ret, bool cbit);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_p1_add_or_double([Out] long[] ret, [In] long[] a, [In] long[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_p1_add_or_double_affine([Out] long[] ret, [In] long[] a,
                                                    [In] long[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_p1_double([Out] long[] ret, [In] long[] a);

public struct P1 {
    internal long[] point;

    private static readonly int sz = (int)blst_p1_sizeof()/sizeof(long);

    //public P1()           { point = new long[sz]; }
    private P1(bool _)      { point = new long[sz]; }
    private P1(P1 p)        { point = (long[])p.point.Clone(); }
    private long[] self()
    {   if (point==null) { point = new long[sz]; } return point;   }

    public P1(SecretKey sk) : this(true)
    {   blst_sk_to_pk_in_g1(point, sk.key);   }
    public P1(byte[] inp) : this(true)
    {   int len = inp.Length;
        if (len == 0 || len != ((inp[0]&0x80) == 0x80 ? P1_COMPRESSED_SZ
                                                      : 2*P1_COMPRESSED_SZ))
            throw new Exception(ERROR.BAD_ENCODING);
        ERROR err = blst_p1_deserialize(point, inp);
        if (err != ERROR.SUCCESS)
            throw new Exception(err);
        blst_p1_from_affine(point, point);
    }
    public P1(P1_Affine affine) : this(true)
    {   blst_p1_from_affine(point, affine.point);   }

    public P1 dup()                 { return new P1(this);                  }
    public P1_Affine to_affine()    { return new P1_Affine(this);           }
    public byte[] serialize()
    {   byte[] ret = new byte[2*P1_COMPRESSED_SZ];
        blst_p1_serialize(ret, point);
        return ret;
    }
    public byte[] compress()
    {   byte[] ret = new byte[P1_COMPRESSED_SZ];
        blst_p1_compress(ret, point);
        return ret;
    }

    public bool on_curve()      { return blst_p1_on_curve(point);           }
    public bool in_group()      { return blst_p1_in_g1(point);              }
    public bool is_inf()        { return blst_p1_is_inf(point);             }
    public bool is_equal(P1 p)  { return blst_p1_is_equal(point, p.point);  }

    public P1 hash_to(byte[] msg, string DST="", byte[] aug=null)
    {   byte[] dst = Encoding.UTF8.GetBytes(DST);
        blst_hash_to_g1(self(), msg, (size_t)msg.Length,
                                dst, (size_t)dst.Length,
                                aug, (size_t)(aug!=null ? aug.Length : 0));
        return this;
    }
    public P1 encode_to(byte[] msg, string DST="", byte[] aug=null)
    {   byte[] dst = Encoding.UTF8.GetBytes(DST);
        blst_encode_to_g1(self(), msg, (size_t)msg.Length,
                                  dst, (size_t)dst.Length,
                                  aug, (size_t)(aug!=null ? aug.Length : 0));
        return this;
    }

    public P1 sign_with(SecretKey sk)
    {   blst_sign_pk_in_g2(point, point, sk.key); return this;   }
    public P1 sign_with(Scalar scalar)
    {   blst_sign_pk_in_g2(point, point, scalar.val); return this;   }

    public void aggregate(P1_Affine inp)
    {   if (blst_p1_affine_in_g1(inp.point))
            blst_p1_add_or_double_affine(point, point, inp.point);
        else
            throw new Exception(ERROR.POINT_NOT_IN_GROUP);
    }

    public P1 mult(byte[] scalar)
    {   blst_p1_mult(point, point, scalar, (size_t)(scalar.Length*8));
        return this;
    }
    public P1 mult(Scalar scalar)
    {   blst_p1_mult(point, point, scalar.val, (size_t)255);
        return this;
    }
    public P1 mult(BigInteger scalar)
    {   byte[] val;
        if (scalar.Sign < 0) {
            val = BigInteger.Negate(scalar).ToByteArray();
            blst_p1_cneg(point, true);
        } else {
            val = scalar.ToByteArray();
        }
        int len = val.Length;
        if (val[len-1]==0) len--;
        blst_p1_mult(point, point, val, (size_t)(len*8));
        return this;
    }
    public P1 cneg(bool flag)   { blst_p1_cneg(point, flag); return this;   }
    public P1 neg()             { blst_p1_cneg(point, true); return this;   }
    public P1 add(P1 a)
    {   blst_p1_add_or_double(point, point, a.point); return this;          }
    public P1 add(P1_Affine a)
    {   blst_p1_add_or_double_affine(point, point, a.point); return this;   }
    public P1 dbl()
    {   blst_p1_double(point, point); return this;                          }

    public static P1 generator()
    {   var ret = new P1(true);
        Marshal.Copy(blst_p1_generator(), ret.point, 0, ret.point.Length);
        return ret;
    }
}

public static P1 G1() { return P1.generator(); }

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_aggregated_in_g1([Out] long[] fp12, [In] long[] p);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ERROR blst_pairing_aggregate_pk_in_g1([In, Out] long[] fp12,
                                [In] long[] pk, [In] long[] sig,
                                [In] byte[] msg, size_t msg_len,
                                [In] byte[] aug, size_t aug_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ERROR blst_pairing_mul_n_aggregate_pk_in_g1([In, Out] long[] fp12,
                                [In] long[] pk, [In] long[] sig,
                                [In] byte[] scalar, size_t nbits,
                                [In] byte[] msg, size_t msg_len,
                                [In] byte[] aug, size_t aug_len);
"""
bottom = """
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern size_t blst_fp12_sizeof();
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_miller_loop([Out] long[] fp12, [In] long[] q,
                                                       [In] long[] p);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_fp12_is_one([In] long[] fp12);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_fp12_is_equal([In] long[] a, [In] long[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_fp12_sqr([Out] long[] ret, [In] long[] a);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_fp12_mul([Out] long[] ret, [In] long[] a,
                                                   [In] long[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_final_exp([Out] long[] ret, [In] long[] a);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_fp12_finalverify([In] long[] a, [In] long[] b);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr blst_fp12_one();
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_fp12_in_group([In] long[] a);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_bendian_from_fp12([Out] byte[] ret, [In] long[] a);

public struct PT {
    internal readonly long[] fp12;

    private static readonly int sz = (int)blst_fp12_sizeof()/sizeof(long);

    internal PT(bool _)     { fp12 = new long[sz]; }
    private PT(PT orig)     { fp12 = (long[])orig.fp12.Clone(); }

    public PT(P1_Affine p) : this(true)
    {   blst_aggregated_in_g1(fp12, p.point);   }
    public PT(P1 p) : this(true)
    {   blst_aggregated_in_g1(fp12, (new P1_Affine(p)).point);   }
    public PT(P2_Affine q) : this(true)
    {   blst_aggregated_in_g2(fp12, q.point);   }
    public PT(P2 q) : this(true)
    {   blst_aggregated_in_g2(fp12, (new P2_Affine(q)).point);   }
    public PT(P2_Affine q, P1_Affine p) : this(true)
    {   blst_miller_loop(fp12, q.point, p.point);   }
    public PT(P1_Affine p, P2_Affine q) : this(q, p) {}
    public PT(P2 q, P1 p) : this(true)
    {   blst_miller_loop(fp12, (new P2_Affine(q)).point,
                               (new P1_Affine(p)).point);
    }
    public PT(P1 p, P2 q) : this(q, p) {}

    public PT dup()         { return new PT(this); }
    public bool is_one()    { return blst_fp12_is_one(fp12); }
    public bool is_equal(PT p)
    {   return blst_fp12_is_equal(fp12, p.fp12);   }
    public PT sqr()         { blst_fp12_sqr(fp12, fp12);         return this; }
    public PT mul(PT p)     { blst_fp12_mul(fp12, fp12, p.fp12); return this; }
    public PT final_exp()   { blst_final_exp(fp12, fp12);        return this; }
    public bool in_group()  { return blst_fp12_in_group(fp12); }
    public byte[] to_bendian()
    {   byte[] ret = new byte[12*P1_COMPRESSED_SZ];
        blst_bendian_from_fp12(ret, fp12);
        return ret;
    }

    public static bool finalverify(PT gt1, PT gt2)
    {   return blst_fp12_finalverify(gt1.fp12, gt2.fp12);   }

    public static PT one()
    {   var ret = new PT(true);
        Marshal.Copy(blst_fp12_one(), ret.fp12, 0, ret.fp12.Length);
        return ret;
    }
}

[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern size_t blst_pairing_sizeof();
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_pairing_init([In, Out] long[] ctx, bool hash_or_encode,
                                             [In] ref long dst, size_t dst_len);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void blst_pairing_commit([In, Out] long[] ctx);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ERROR blst_pairing_merge([In, Out] long[] ctx, [In] long[] ctx1);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool blst_pairing_finalverify([In] long[] ctx, [In] long[] sig);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern
void blst_pairing_raw_aggregate([In, Out] long[] ctx, [In] long[] q,
                                                      [In] long[] p);
[DllImport("blst.dll", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr blst_pairing_as_fp12([In] long[] ctx);

public struct Pairing {
    private readonly long[] ctx;

    private static readonly int sz = (int)blst_pairing_sizeof()/sizeof(long);

    public Pairing(bool hash_or_encode=false, string DST="")
    {
        byte[] dst = Encoding.UTF8.GetBytes(DST);
        int dst_len = dst.Length;
        int add_len = dst_len!=0 ? (dst_len+sizeof(long)-1)/sizeof(long) : 1;
        Array.Resize(ref dst, add_len*sizeof(long));

        ctx = new long[sz+add_len];

        for (int i=0; i<add_len; i++)
            ctx[sz+i] = BitConverter.ToInt64(dst, i*sizeof(long));

        GCHandle h = GCHandle.Alloc(ctx, GCHandleType.Pinned);
        blst_pairing_init(ctx, hash_or_encode, ref ctx[sz], (size_t)dst_len);
        h.Free();
    }

    public ERROR aggregate(P1_Affine pk, Nullable<P2_Affine> sig,
                                         byte[] msg, byte[] aug=null)
    {   return blst_pairing_aggregate_pk_in_g1(ctx, pk.point,
                                sig.HasValue ? sig.Value.point : null,
                                msg, (size_t)msg.Length,
                                aug, (size_t)(aug!=null ? aug.Length : 0));
    }
    public ERROR aggregate(P2_Affine pk, Nullable<P1_Affine> sig,
                                         byte[] msg, byte[] aug=null)
    {   return blst_pairing_aggregate_pk_in_g2(ctx, pk.point,
                                sig.HasValue ? sig.Value.point : null,
                                msg, (size_t)msg.Length,
                                aug, (size_t)(aug!=null ? aug.Length : 0));
    }
    public ERROR mul_n_aggregate(P2_Affine pk, P1_Affine sig,
                                               byte[] scalar, int nbits,
                                               byte[] msg, byte[] aug=null)
    {   return blst_pairing_mul_n_aggregate_pk_in_g2(ctx, pk.point, sig.point,
                                scalar, (size_t)nbits,
                                msg, (size_t)msg.Length,
                                aug, (size_t)(aug!=null ? aug.Length : 0));
    }
    public ERROR mul_n_aggregate(P1_Affine pk, P2_Affine sig,
                                               byte[] scalar, int nbits,
                                               byte[] msg, byte[] aug=null)
    {   return blst_pairing_mul_n_aggregate_pk_in_g1(ctx, pk.point, sig.point,
                                scalar, (size_t)nbits,
                                msg, (size_t)msg.Length,
                                aug, (size_t)(aug!=null ? aug.Length : 0));
    }

    public void commit()    { blst_pairing_commit(ctx); }
    public void merge(Pairing a)
    {   var err = blst_pairing_merge(ctx, a.ctx);
        if (err != ERROR.SUCCESS)
            throw new Exception(err);
    }
    public bool finalverify(PT sig=new PT())
    {   return blst_pairing_finalverify(ctx, sig.fp12);   }

    public void raw_aggregate(P2_Affine q, P1_Affine p)
    {   blst_pairing_raw_aggregate(ctx, q.point, p.point);   }
    public void raw_aggregate(P1_Affine p, P2_Affine q)
    {   raw_aggregate(q, p);   }
    public void raw_aggregate(P2 q, P1 p)
    {   blst_pairing_raw_aggregate(ctx, (new P2_Affine(q)).point,
                                        (new P1_Affine(p)).point);
    }
    public void raw_aggregate(P1 p, P2 q)
    {   raw_aggregate(q, p);   }
    public PT as_fp12()
    {   var ret = new PT(true);
        GCHandle h = GCHandle.Alloc(ctx, GCHandleType.Pinned);
        Marshal.Copy(blst_pairing_as_fp12(ctx), ret.fp12, 0, ret.fp12.Length);
        h.Free();
        return ret;
    }
}
}}"""

here = re.split(r'[/\\](?=[^/\\]*$)', sys.argv[0])
if len(here) > 1:
    os.chdir(here[0])


def xchg_1vs2(matchobj):
    if matchobj.group(2) == '1':
        return matchobj.group(1) + '2'
    else:
        return matchobj.group(1) + '1'


def newer(files):
    if len(files) == 1:
        return True
    rh = files[-1]
    if not os.path.exists(rh):
        return True
    for lh in files[:-1]:
        if os.stat(lh).st_ctime > os.stat(rh).st_ctime:
            return True
    return False


fname = "supranational.blst.cs"
if newer([here[-1], fname]):
    fd = open(fname, "w")
    print("//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", file=fd)
    print("// DO NOT EDIT THIS FILE!!!",                         file=fd)
    print("// The file is auto-generated by " + here[-1],        file=fd)
    print("//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", file=fd)
    print("\n\n", file=fd)
    print(top,    file=fd)
    print(middle, file=fd)
    print(re.sub(r'((?<!f)[pgPG])([12])', xchg_1vs2, middle), file=fd)
    print(bottom, file=fd)
    fd.close()

try:  # mono-devel on Linux ["-language:5" corresponds to latest ECMA/ISO]
    if newer([fname, "poc.cs", "poc.exe"]):
        subprocess.check_call(["mcs", "-langversion:5", "-optimize+",
                                      "poc.cs", fname, "-r:System.Numerics.dll"])
    if newer(["../blst.h"] + glob.glob("libblst.dll.*")):
        print("building libblst.dll...") or sys.stdout.flush()
        subprocess.check_call(["../../build.sh", "-dll"] + sys.argv[1:])
    subprocess.check_call(["mono", "poc.exe"])
    sys.exit(0)
except OSError as e:
    if e.errno != 2:    # not "no such file or directory"
        raise e

try:  # Visual Studio Developer Command Prompt
    if newer([fname, "poc.cs", "poc.exe"]):
        subprocess.check_call(["csc", "-langversion:5", "-optimize+",
                                      "poc.cs", fname, "-r:System.Numerics.dll"])
    if newer([os.path.normpath("../blst.h"), "blst.dll"]):
        print("building blst.dll...") or sys.stdout.flush()
        subprocess.check_call([os.path.normpath("../../build.bat"), "-shared"]
                              + sys.argv[1:])
    subprocess.check_call(os.path.normpath("./poc.exe"))
    sys.exit(0)
except OSError as e:
    if e.errno != 2:    # not "no such file or directory"
        raise e

# env = os.environ.copy()
# env["PATH"] = os.getcwd() + os.path.pathsep + env["PATH"]
# env["DYLD_FALLBACK_LIBRARY_PATH"] = os.getcwd()
# env["LD_LIBRARY_PATH"] = os.getcwd()
# subprocess.check_call(["dotnet", "run"], env=env)
