// register A - initially loaded with a
        // register B - initially loaded with b
        // register C - initially 0, exactly one bit wider than A, stores overflow bit
        // register N - initially loaded with N
        // after computation in register B: (a+b) mod N
        // other registers dont change their states
        // register B must be exactly one bit wider than A to store carry bit
        // register B must be exactly one bit wider than N to store carry bit
        // registers A, N must be the same length
        // Insecure version: registers widths etc. are not checked
        public static void AddModulo(
            this QuantumComputer comp,
            Register a,
            Register b,
            Register c,
            Register N,
            ulong valueN)
        {
            RegisterRef carry = b[b.Width - 1];
            RegisterRef overflow = c[c.Width - 1];

            comp.Add(a, b, c);
            comp.InverseAdd(N, b, c);
            comp.SigmaX(carry);
            comp.CNot(overflow, carry);
            comp.SigmaX(carry);

            //resetting N
            comp.LoadNumber(N, valueN, overflow);

            comp.Add(N, b, c);

            // now we have [(a+b) mod N] in B register
            // next steps lead to recover the initial state of registers N and overflow bit

            //setting N back
            comp.LoadNumber(N, valueN, overflow);

            comp.InverseAdd(a, b, c);
            comp.CNot(overflow, carry);
            comp.Add(a, b, c);
        }
        // controlled loading a number into register
        // using Toffoli gates
        // if controlBits are empty, the number is loaded unconditionally
        public static void LoadNumber(this QuantumComputer comp, Register target, ulong number, params RegisterRef[] controlBits)
        {
            Validate(target, number);

            int controlLength = controlBits.Length;

            int i = 0;
            ulong tmpN = number;
            while (tmpN > 0)
            {
                int rest = (int)(tmpN % 2);
                tmpN = tmpN / 2;

                if (rest == 1)
                {
                    if (controlLength > 1)
                    {
                        comp.Toffoli(target[i], controlBits);
                    }
                    else if (controlLength > 0)
                    {
                        comp.CNot(target[i], controlBits[0]);
                    }
                    else
                    {
                        target.SigmaX(i);
                    }
                }
                i++;
            }
        }
        // Add(a, b, 0) -> (a, a+b, 0)
        // Registers a, b and c must not overlap
        // Registers a and b have the same width
        // Register c is used for storing carries and must be minimum one bit wider than register a (or b)
        // Initial value of c must be 0
        public static void Add(this QuantumComputer comp,
            Register a, Register b, Register c)
        {
            if (comp.Group)
            {
                object[] parameters = new object[] { comp, a, b, c };
                comp.AddParametricGate("Add", parameters);
                return;
            }
            else
            {
                comp.Group = true;
            }

            int width = a.Width;
            int i = 0;
            for (; i < width - 1; i++)
            {
                comp.Carry(c[i], a[i], b[i], c[i + 1]);
            }
            comp.Carry(c[i], a[i], b[i], b[i + 1]);

            comp.CNot(b[i], a[i]);
            comp.Sum(c[i], a[i], b[i]);
            i--;
            for (; i >= 0; i--)
            {
                comp.InverseCarry(c[i], a[i], b[i], c[i + 1]);
                comp.Sum(c[i], a[i], b[i]);
            }
        }
        // register A - initially loaded with a
        // register B - initially loaded with b
        // register C - initially 0, exactly one bit wider than A, stores overflow bit
        // register N - initially loaded with N
        // after computation in register B: (a+b) mod N
        // other registers dont change their states
        // register B must be exactly one bit wider than A to store carry bit
        // register B must be exactly one bit wider than N to store carry bit
        // registers A, N must be the same length
        // Insecure version: registers widths etc. are not checked
        public static void AddModulo(
            this QuantumComputer comp,
            Register a,
            Register b,
            Register c,
            Register N,
            ulong valueN)
        {
            if (comp.Group)
            {
                object[] parameters = new object[] { comp, a, b, c, N, valueN };
                comp.AddParametricGate("AddModulo", parameters);
                return;
            }
            else
            {
                comp.Group = true;
            }

            RegisterRef carry = b[b.Width - 1];
            RegisterRef overflow = c[c.Width - 1];

            comp.Add(a, b, c);
            comp.InverseAdd(N, b, c);
            comp.SigmaX(carry);
            comp.CNot(overflow, carry);
            comp.SigmaX(carry);

            //resetting N
            comp.LoadNumber(N, valueN, overflow);

            comp.Add(N, b, c);

            // now we have [(a+b) mod N] in B register
            // next steps lead to recover the initial state of registers N and overflow bit

            //setting N back
            comp.LoadNumber(N, valueN, overflow);

            comp.InverseAdd(a, b, c);
            comp.CNot(overflow, carry);
            comp.Add(a, b, c);
        }
        public static void AddModuloQFTPhi(this QuantumComputer comp, ulong a, ulong N, RegisterRef ctrl, Register b, params RegisterRef[] controls)
        {
            comp.AddQFTPhi(a, b, controls);
            comp.InverseAddQFTPhi(N, b);

            comp.InverseQFT(b);
            comp.CNot(ctrl, b[b.Width - 1]);
            comp.QFT(b);

            comp.AddQFTPhi(N, b, ctrl);
            comp.InverseAddQFTPhi(a, b, controls);

            comp.InverseQFT(b);
            comp.SigmaX(b[b.Width - 1]);
            comp.CNot(ctrl, b[b.Width - 1]);
            comp.SigmaX(b[b.Width - 1]);
            comp.QFT(b);

            comp.AddQFTPhi(a, b, controls);
        }
        // controlled loading a number into register
        // using Toffoli gates
        // if controlBits are empty, the number is loaded unconditionally
        public static void LoadNumber(this QuantumComputer comp, Register target, ulong number, params RegisterRef[] controlBits)
        {
            if (comp.Group)
            {
                object[] parameters = new object[] { comp, target, number, controlBits };
                comp.AddParametricGate("LoadNumber", parameters);
                return;
            }
            else
            {
                comp.Group = true;
            }

            Validate(target, number);

            int controlLength = controlBits.Length;

            int i = 0;
            ulong tmpN = number;
            while (tmpN > 0)
            {
                int rest = (int)(tmpN % 2);
                tmpN = tmpN / 2;

                if (rest == 1)
                {
                    if (controlLength > 1)
                    {
                        comp.Toffoli(target[i], controlBits);
                    }
                    else if (controlLength > 0)
                    {
                        comp.CNot(target[i], controlBits[0]);
                    }
                    else
                    {
                        target.SigmaX(i);
                    }
                }
                i++;
            }
        }
        /// <summary>
        /// <para>
        /// Adds two registers. The result is stored in the second. An extra register is needed for storing carry bits.
        /// </para>
        /// <para>
        /// Add(a, b, 0) -> (a, a+b, 0)
        /// </para>
        /// <para>
        /// In order to improve performance, this method do not check if arguments are valid.
        /// They must satisfy following conditions:
        /// <list type="bullet">
        /// <item>Registers a, b and c must not overlap</item>
        /// <item>Registers a and c must have the same width</item>
        /// <item>Register b must be exactly one bit wider than register a (or c)</item>
        /// <item>Initial value of c must be 0</item>
        /// </list>
        /// </para>
        /// </summary>
        /// <param name="comp">The QuantumComputer instance.</param>
        /// <param name="a">The first register to sum. Its value remains unchanged.</param>
        /// <param name="b">The second register to sum. After performing this operation, it contains the sum result.</param>
        /// <param name="c">The extra register for storing carry bits.</param>
        public static void Add(this QuantumComputer comp, 
            Register a, Register b, Register c)
        {
            int width = a.Width;
            int i = 0;
            for (; i < width - 1; i++)
            {
                comp.Carry(c[i], a[i], b[i], c[i + 1]);
            }
            comp.Carry(c[i], a[i], b[i], b[i + 1]);

            comp.CNot(b[i], a[i]);
            comp.Sum(c[i], a[i], b[i]);
            i--;
            for (; i >= 0; i--)
            {
                comp.InverseCarry(c[i], a[i], b[i], c[i + 1]);
                comp.Sum(c[i], a[i], b[i]);
            }
        }
        // Insecure version: registers widths etc. are not checked
        public static void InverseAddModulo(
            this QuantumComputer comp,
            Register a,
            Register b,
            Register c,
            Register N,
            ulong valueN)
        {
            RegisterRef carry = b[b.Width - 1];
            RegisterRef overflow = c[c.Width - 1];

            comp.InverseAdd(a, b, c);
            comp.CNot(overflow, carry);
            comp.Add(a, b, c);

            //resetting N:
            comp.LoadNumber(N, valueN, overflow);

            comp.InverseAdd(N, b, c);

            //setting N back:
            comp.LoadNumber(N, valueN, overflow);

            comp.SigmaX(carry);
            comp.CNot(overflow, carry);
            comp.SigmaX(carry);
            comp.Add(N, b, c);
            comp.InverseAdd(a, b, c);
        }
        public static void AddModuloQFTPhi(this QuantumComputer comp, ulong a, ulong N, RegisterRef ctrl, Register b, params RegisterRef[] controls)
        {
            if (comp.Group)
            {
                object[] parameters = new object[] { comp, a, N, ctrl, b, controls };
                comp.AddParametricGate("AddModuloQFTPhi", parameters);
                return;
            }
            else
            {
                comp.Group = true;
            }

            comp.AddQFTPhi(a, b, controls);
            comp.InverseAddQFTPhi(N, b);

            comp.InverseQFT(b);
            comp.CNot(ctrl, b[b.Width - 1]);
            comp.QFT(b);

            comp.AddQFTPhi(N, b, ctrl);
            comp.InverseAddQFTPhi(a, b, controls);

            comp.InverseQFT(b);
            comp.SigmaX(b[b.Width - 1]);
            comp.CNot(ctrl, b[b.Width - 1]);
            comp.SigmaX(b[b.Width - 1]);
            comp.QFT(b);

            comp.AddQFTPhi(a, b, controls);
        }
        // Insecure version: registers widths etc. are not checked
        public static void InverseAddModulo(
            this QuantumComputer comp,
            Register a,
            Register b,
            Register c,
            Register N,
            ulong valueN)
        {
            if (comp.Group)
            {
                object[] parameters = new object[] { comp, a, b, c, N, valueN };
                comp.AddParametricGate("InverseAddModulo", parameters);
                return;
            }
            else
            {
                comp.Group = true;
            }

            RegisterRef carry = b[b.Width - 1];
            RegisterRef overflow = c[c.Width - 1];

            comp.InverseAdd(a, b, c);
            comp.CNot(overflow, carry);
            comp.Add(a, b, c);

            //resetting N:
            comp.LoadNumber(N, valueN, overflow);

            comp.InverseAdd(N, b, c);

            //setting N back:
            comp.LoadNumber(N, valueN, overflow);

            comp.SigmaX(carry);
            comp.CNot(overflow, carry);
            comp.SigmaX(carry);
            comp.Add(N, b, c);
            comp.InverseAdd(a, b, c);
        }