
#include <mystdlib.h>
#include <meshing.hpp>
#include "bcfunctions.hpp"


namespace netgen
{
   // Default colour to be used for boundary condition number "0"
   #define DEFAULT_R       0.0
   #define DEFAULT_G       1.0
   #define DEFAULT_B       0.0

   // Boundary condition number to use if a face does not have a 
   // colour assigned to it, or if the colour is the above defined 
   // default colour
   #define DEFAULT_BCNUM   1

   // Default tolerance for colour matching (using Euclidean distance)
   #define DEFAULT_EPS     2.5e-05




   /*! Philippose - 11/07/2009
       Function to check if two RGB colours are equal

       Note#1: Currently uses unweighted Euclidean Distance 
       for colour matching.

       Note#2: The tolerance used for deciding whether two 
       colours match is defined as "eps" and is currently 
       2.5e-5 (for square of distance)
   */
   bool ColourMatch(Vec<4> col1, Vec<4> col2, double eps)
   {
      if(eps <= 0.0) eps = DEFAULT_EPS;
      
      bool colmatch = false;

      if((col1-col2).Length2() < eps) colmatch = true;

      return colmatch;
   }
      




   /*! Philippose - 11/07/2009
       Function to create a list of all the unique colours 
       available in a given mesh
   */
  void GetFaceColours(Mesh & mesh, NgArray<Vec<4>> & face_colours)
   {
      face_colours.SetSize(1);
      face_colours.Elem(1) = mesh.GetFaceDescriptor(1).SurfColour();
      
      for(int i = 1; i <= mesh.GetNFD(); i++)
      {
         auto face_colour = mesh.GetFaceDescriptor(i).SurfColour();
         bool col_found = false;
         
         for(int j = 1; j <= face_colours.Size(); j++)
         {
            if(ColourMatch(face_colours.Elem(j),face_colour))
            {
               col_found = true;
               break;
            }
         }
         
         if(!col_found) face_colours.Append(face_colour);
      }

      if(printmessage_importance >= 3)
      {
         cout << endl << "-------- Face Colours --------" << endl;
         for( int i = 1; i <= face_colours.Size(); i++)
         {
            cout << face_colours.Elem(i) << endl;
         }
         cout << "------------------------------" << endl;
      }
   }






   /*! Philippose - 11/07/2009
       Assign boundary condition numbers based on a user defined 
       colour profile file.

       The default profile file is "netgen.ocf"

       If the mesh contains colours not defined in the profile,
       netgen automatically starts assigning each new colour a 
       new boundary condition number starting from the highest 
       boundary condition number specified in the profile file.
   */
   void AutoColourAlg_UserProfile(Mesh & mesh, ifstream & ocf)
   {
      char ocf_inp[100];
      bool header_found = false;

      // Number of colour specifications in the 
      // user profile file
      int numentries = 0;
      while((ocf.good()) && (!header_found))
      {
         ocf >> ocf_inp;
         if(strcmp(ocf_inp,"boundary_colours") == 0) header_found = true;
      }

      if(!header_found)
      {
         ocf.close();
         throw NgException("AutoColourAlg_UserProfile: Invalid or empty Boundary Colour Profile file\n");
         return;
      }

      // Read in the number of entries from file
      ocf >> numentries;
      if(numentries > 0)
      {
         if(!ocf.good())
         {
            ocf.close();
            throw NgException("AutoColourAlg_UserProfile: Invalid or empty Boundary Colour Profile file\n");
            return;
         }

         PrintMessage(3, "Number of colour entries: ", numentries);
      }
      else
      {
         ocf.close();
         PrintMessage(3, "AutoColourAlg_UserProfile: No Boundary Colour entries found.... no changes made!");
         return;
      }

      // Arrays to hold the specified RGB colour triplets as well 
      // as the associated boundary condition number
      NgArray<Vec<4>> bc_colours(numentries);
      NgArray<int> bc_num(numentries);
      NgArray<bool> bc_used(numentries);
      
      // Actually read in the data from the file
      for(int i = 1; i <= numentries; i++)
      {
         int bcnum;
         // double col_red, col_green, col_blue;

         ocf >> bcnum;
         // Boundary condition number DEFAULT_BCNUM is reserved for 
         // faces which have the default colour Green (0.0,1.0,0.0)
         // To prevent confusion, no boundary numbery below this default 
         // are permitted
         if(bcnum < (DEFAULT_BCNUM + 1)) bcnum = DEFAULT_BCNUM+1;

         bc_num.Elem(i) = bcnum;
         bc_used.Elem(i) = false;
         ocf >> bc_colours.Elem(i)[0]
             >> bc_colours.Elem(i)[1]
             >> bc_colours.Elem(i)[2];

         if(!ocf.good())
         {
            ocf.close();
            throw NgException("Boundary Colour file error: Number of entries do not match specified list size!!\n");
            return;
         }

         // Bound checking of the values
         // The RGB values should be between 0.0 and 1.0
         for(auto i : Range(3))
           bc_colours.Elem(bcnum)[i] = max2(min2(bc_colours.Elem(bcnum)[i], 1.), 0.);
      }

      PrintMessage(3, "Successfully loaded Boundary Colour Profile file....");
      ocf.close();

      // Find the highest boundary condition number in the list
      // All colours in the geometry which are not specified in the 
      // list will be given boundary condition numbers higher than this 
      // number
      int max_bcnum = DEFAULT_BCNUM;
      for(int i = 1; i <= bc_num.Size();i++)
      {
         if(bc_num.Elem(i) > max_bcnum) max_bcnum = bc_num.Elem(i);
      }

      PrintMessage(3, "Highest boundary number in list = ",max_bcnum);

      NgArray<Vec<4>> all_colours;
      
      // Extract all the colours to see how many there are
      GetFaceColours(mesh,all_colours);
      PrintMessage(3,"\nNumber of colours defined in Mesh: ", all_colours.Size());

      if(all_colours.Size() == 0)
      {
         PrintMessage(3,"No colour data detected in Mesh... no changes made!");
         return;
      }

      int nfd = mesh.GetNFD();

      for(int face_index = 1; face_index <= nfd; face_index++)
      {
         // Get the colour of the face being currently processed
         auto face_colour = mesh.GetFaceDescriptor(face_index).SurfColour();
         if(!ColourMatch(face_colour,Vec<4>(DEFAULT_R,DEFAULT_G,DEFAULT_B, 1.0)))
         {
            // Boolean variable to check if the boundary condition was applied 
            // or not... not applied would imply that the colour of the face 
            // does not exist in the list of colours in the profile file
            bool bc_assigned = false;

            for(int col_index = 1; col_index <= bc_colours.Size(); col_index++)
            {
               if((ColourMatch(face_colour,bc_colours.Elem(col_index))) && (!bc_assigned))
               {
                  mesh.GetFaceDescriptor(face_index).SetBCProperty(bc_num.Elem(col_index));
                  bc_used.Elem(col_index) = true;
                  bc_assigned = true;
                  break;
               }
            }

            // If the colour was not found in the list, add it to the list, and assign 
            // the next free boundary condition number to it
            if(!bc_assigned)
            {
               max_bcnum++;
               bc_num.Append(max_bcnum);
               bc_colours.Append(face_colour);
               bc_used.Append(true);

               mesh.GetFaceDescriptor(face_index).SetBCProperty(max_bcnum);
            }
         }
         else
         {
            // Set the boundary condition number to the default one
            mesh.GetFaceDescriptor(face_index).SetBCProperty(DEFAULT_BCNUM);
         }
      }

      // User Information of the results of the operation
      Vec<4> ref_colour(0.0,1.0,0.0,1.0);
      PrintMessage(3,"Colour based Boundary Condition Property details:");
      for(int bc_index = 0; bc_index <= bc_num.Size(); bc_index++)
      {
         if(bc_index > 0) ref_colour = bc_colours.Elem(bc_index);

         if(bc_index == 0) 
         {
            PrintMessage(3, "BC Property: ",DEFAULT_BCNUM);
            PrintMessage(3, "   RGB Face Colour = ",Vec3d{ref_colour[0], ref_colour[1], ref_colour[2]},"","\n");
         }
         else if(bc_used.Elem(bc_index))
         {
            PrintMessage(3, "BC Property: ",bc_num.Elem(bc_index));
            PrintMessage(3, "   RGB Face Colour = ",Vec3d{ref_colour[0], ref_colour[1], ref_colour[2]},"","\n");
         }
      }
   }




   
   /*! Philippose - 11/07/2009
       Assign boundary condition numbers based on the colours 
       assigned to each face in the mesh using an automated 
       algorithm.

       The particular algorithm used has been briefly explained 
       in the header file "occauxfunctions.hpp"
   */
   void AutoColourAlg_Sorted(Mesh & mesh)
   {
      NgArray<Vec<4>> all_colours;
      NgArray<int> faces_sorted;
      NgArray<int> colours_sorted;

      // Extract all the colours to see how many there are
      GetFaceColours(mesh,all_colours);

      // Delete the default colour from the list since it will be accounted 
      // for automatically
      for(int i = 1; i <= all_colours.Size(); i++)
      {
        if(ColourMatch(all_colours.Elem(i),Vec<4>(DEFAULT_R,DEFAULT_G,DEFAULT_B,1.0)))
         {
            all_colours.DeleteElement(i);
            break;
         }
      }
      PrintMessage(3,"\nNumber of colours defined in Mesh: ", all_colours.Size());

      if(all_colours.Size() == 0)
      {
         PrintMessage(3,"No colour data detected in Mesh... no changes made!");
         return;
      }

      // One more slot than the number of colours are required, to 
      // account for individual faces which have no colour data 
      // assigned to them in the CAD software
      faces_sorted.SetSize(all_colours.Size()+1);
      colours_sorted.SetSize(all_colours.Size()+1);
      faces_sorted = 0;
      
      // Index NgArray to identify the colours the faces were assigned to, 
      // after the bubble sort routine to sort the automatic boundary 
      // identifiers according to the number of surface mesh elements 
      // of a given colour
      for(int i = 0; i <= all_colours.Size(); i++) colours_sorted[i] = i;

      // Used to hold the number of surface elements without any OCC 
      // colour definition
      int no_colour_faces = 0;

      // Index in the faces array assigned to faces without any 
      // or the default colour definition
      int no_colour_index = 0;

      int nfd = mesh.GetNFD();

      // Extract the number of surface elements having a given colour
      // And save this number into an array for later sorting
      for(int face_index = 1; face_index <= nfd; face_index++)
        {
          Array<SurfaceElementIndex> se_face;
          
         mesh.GetSurfaceElementsOfFace(face_index, se_face);

         auto face_colour = mesh.GetFaceDescriptor(face_index).SurfColour();
         if(!ColourMatch(face_colour,Vec<4>(DEFAULT_R,DEFAULT_G,DEFAULT_B,1.0)))
         {
            for(int i = 1; i <= all_colours.Size(); i++)
            {
               if(ColourMatch(face_colour, all_colours.Elem(i)))
               {
                  faces_sorted[i] = faces_sorted[i] + se_face.Size();
               }
            }
         }
         else
         {
            // Add the number of surface elements without any colour 
            // definition separately
            no_colour_faces = no_colour_faces + se_face.Size();
         }
      }

      // Sort the face colour indices according to the number of surface 
      // mesh elements which have a specific colour
      BubbleSort(faces_sorted,colours_sorted);

      // Now update the array position assigned for surface elements 
      // without any colour definition with the number of elements
      faces_sorted[no_colour_index] = no_colour_faces;

      // Now actually assign the BC Property to the respective faces
      for(int face_index = 1; face_index <= nfd; face_index++)
      {
         auto face_colour = mesh.GetFaceDescriptor(face_index).SurfColour();
         if(!ColourMatch(face_colour,Vec<4>(DEFAULT_R,DEFAULT_G,DEFAULT_B, 1.0)))
         {
            for(int i = 0; i < colours_sorted.Size(); i++)
            {
               Vec<4> ref_colour;
               if(i != no_colour_index) ref_colour = all_colours.Elem(colours_sorted[i]);

               if(ColourMatch(face_colour, ref_colour))
               {
                  mesh.GetFaceDescriptor(face_index).SetBCProperty(i + DEFAULT_BCNUM);
               }
            }
         }
         else
         {
            mesh.GetFaceDescriptor(face_index).SetBCProperty(DEFAULT_BCNUM);
         }

         PrintMessage(4,"Face number: ",face_index," ; BC Property = ",mesh.GetFaceDescriptor(face_index).BCProperty());
      }

      // User Information of the results of the operation
      Vec<4> ref_colour(0.0,1.0,0.0,1.0);
      PrintMessage(3,"Colour based Boundary Condition Property details:");
      for(int i = 0; i < faces_sorted.Size(); i++)
      {
         if(colours_sorted[i] > 0) ref_colour = all_colours.Elem(colours_sorted[i]);

         PrintMessage(3, "BC Property: ",i + DEFAULT_BCNUM);
         PrintMessage(3, "   Nr. of Surface Elements = ", faces_sorted[i]);
         PrintMessage(3, "   Colour Index = ", colours_sorted[i]);
         PrintMessage(3, "   RGB Face Colour = ",Vec3d{ref_colour[0], ref_colour[1], ref_colour[2]},"","\n");
      }
   }





   /*! Philippose - 13/07/2009
       Main function implementing automated assignment of 
       Boundary Condition numbers based on face colours

       This functionality is currently implemtented at the mesh 
       level, and hence allows colour based assignment of boundary 
       conditions for any geometry type within netgen which 
       supports face colours
   */
   void AutoColourBcProps(Mesh & mesh, const char * bccolourfile)
   {
      // Go directly to the alternate algorithm if no colour profile file was specified
      if(!bccolourfile)
      {
         PrintMessage(1,"AutoColourBcProps: Using Automatic Colour based boundary property assignment algorithm");
         AutoColourAlg_Sorted(mesh);
      }
      else
      {
         ifstream ocf(bccolourfile);

         // If there was an error opening the Colour profile file, jump to the alternate 
         // algorithm after printing a message
         if(!ocf)
         {
            PrintMessage(1,"AutoColourBcProps: Error loading Boundary Colour Profile file ", 
                         bccolourfile, " ....","Switching to Automatic Assignment algorithm!");

            AutoColourAlg_Sorted(mesh);
         }
         // If the file opens successfully, call the function which assigns boundary conditions 
         // based on the colour profile file
         else
         {
            PrintMessage(1, "AutoColourBcProps: Using Boundary Colour Profile file: ");
            PrintMessage(1, "  ", bccolourfile);
            AutoColourAlg_UserProfile(mesh, ocf);

            // Make sure the file is closed before exiting the function
            if(ocf.is_open())
            {
               ocf.close();
            }
         }
      }
   }
}
