//------------------------------------------------------------------------------
// Copyright (C) 2017 Intel Corporation 
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//------------------------------------------------------------------------------


// Include the header file including the declaration of the QubitRegister class.
#include "../include/qureg.hpp"

// Start of the main program (C++ language).
int main(int argc, char **argv)
{
  // Create the MPI environment, passing the same argument to all the ranks.
  iqs::mpi::Environment env(argc, argv);
  // qHiPSTER is structured so that only even number of ranks are used to store
  // and manipulate the quantum state. In case the number of ranks is not supported,
  // try to decrease it by 1 until it is.
  if (env.IsUsefulRank() == false) return 0;
  // qHiPSTER has functions that simplify some MPI instructions. However, it is important
  // to keep trace of the current rank.
  int myid = env.GetRank();


  // The number of qubits in the quantum register is provided by an integer variable.
  // Notice that qubits will have indices: 0, 1, 2, ..., N-1.
  int N = 4;
  std::size_t tmpSize = 0;

   //// Grover Search EXAMPLE ////

      if (myid == 0) printf("\n --- GROVER EXAMPLE --- \n");
      int Ngrover = 4;
      // Create the state of a quantum register, having N qubits.
      // The state is initialized as a computational basis state (using the keyword "base")
      // corresponding to the index 0. The index corresponds to a N-bit integer in decimal
      // representation. With N qubits there are 2^N indices, from 0 to 2^{N-1}.
      iqs::QubitRegister<ComplexDP> psig(Ngrover, "base", 0);
	  
      psig.Print("Initial State =");

      //////////////////////
      // Initialization  ///
      //////////////////////

      // Let us apply an Hadamard gate on all other qubits.
      for (unsigned q=0; q<Ngrover; ++q)
      {
          psig.ApplyHadamard(q);
      }
      psig.Print("After Hadamard =");
	  
      /////////////////////////////////////////////////////////
      // Oracle for the state |0>_0 . |1>_1 . |0>_2 . |0>_3 ///
      /////////////////////////////////////////////////////////

      psig.ApplyPauliX(0);
      psig.ApplyPauliX(2);
      psig.ApplyPauliX(3);
		
		
      double PI_4 = M_PI/4.0;
	  
      psig.ApplyCPhaseRotation(0,3,PI_4);
      psig.ApplyCPauliX(0,1);
      psig.ApplyCPhaseRotation(1,3,-PI_4);
      psig.ApplyCPauliX(0,1);
      psig.ApplyCPhaseRotation(1,3,PI_4);
      psig.ApplyCPauliX(1,2);
      psig.ApplyCPhaseRotation(2,3,-PI_4);
      psig.ApplyCPauliX(0,2);
      psig.ApplyCPhaseRotation(2,3,PI_4);
      psig.ApplyCPauliX(1,2);
      psig.ApplyCPhaseRotation(2,3,-PI_4);
      psig.ApplyCPauliX(0,2);
      psig.ApplyCPhaseRotation(2,3,PI_4);
  
      psig.ApplyPauliX(0);
      psig.ApplyPauliX(2);
      psig.ApplyPauliX(3);
		 
      /////////////////////
      // Amplification  ///
      /////////////////////
      for (unsigned q=0; q<Ngrover; ++q)
      {
          psig.ApplyHadamard(q);
      }
		
      for (unsigned q=0; q<Ngrover; ++q)
      {
          psig.ApplyPauliX(q);
      }
		
      /////////////////
      // CCCZ Gate  ///
      /////////////////
      
      psig.ApplyCPhaseRotation(0,3,PI_4);
      psig.ApplyCPauliX(0,1);
      psig.ApplyCPhaseRotation(1,3,-PI_4);
      psig.ApplyCPauliX(0,1);
      psig.ApplyCPhaseRotation(1,3,PI_4);
      psig.ApplyCPauliX(1,2);
      psig.ApplyCPhaseRotation(2,3,-PI_4);
      psig.ApplyCPauliX(0,2);
      psig.ApplyCPhaseRotation(2,3,PI_4);
      psig.ApplyCPauliX(1,2);
      psig.ApplyCPhaseRotation(2,3,-PI_4);
      psig.ApplyCPauliX(0,2);
      psig.ApplyCPhaseRotation(2,3,PI_4);

      /////////////////////
      // End CCCZ Gate  ///
      /////////////////////

      for (unsigned q=0; q<Ngrover; ++q)
      {
          psig.ApplyPauliX(q);
      }
      for (unsigned q=0; q<Ngrover; ++q)
      {
          psig.ApplyHadamard(q);
      }

      psig.Print("Measurement =");
}
