!Contains information about output
MODULE moduleOutput
  ! Path and folder for the output
  CHARACTER(:), ALLOCATABLE:: path
  CHARACTER(:), ALLOCATABLE:: folder
  ! Number of digits for step files
  INTEGER:: iterationDigits
  CHARACTER(:), ALLOCATABLE:: iterationFormat
  ! Triggers and counters for output
  INTEGER:: triggerOutput,  counterOutput  = 0
  INTEGER:: triggerCPUTime, counterCPUTime = 0
  ! logicals to activate file output
  LOGICAL:: timeOutput = .FALSE.
  LOGICAL:: collOutput = .FALSE.
  LOGICAL:: emOutput   = .FALSE.
  logical:: boundaryParticleOutput = .false.
  logical:: boundaryEMOutput = .false.
  logical:: injectOutput = .false.

  ! Prefix for iteration files
  character(len=*), parameter:: prefix = 'Step'

  ! Column separator
  character(len=*), parameter:: colSep = '","'

  ! General format for file outputs
  character(len=*), parameter:: fmtColReal = 'ES0.6E3,:,'//colSep ! Column with real
  character(len=*), parameter:: fmtColInt  = 'I0,:,'//colSep      ! Column with integer
  character(len=*), parameter:: fmtColStr  = 'A,:,'//colSep       ! Column with text
  character(len=*), parameter:: fmtReal    = 'ES14.6E3'           ! Fixed size real
  character(len=*), parameter:: fmtInt     = 'I14'                ! Fixed size real

  ! File IDs for different input/output
  integer, parameter:: fileID_mesh = 10             ! Base id for mesh files
  integer, parameter:: fileID_output = 20           ! Base id for species/collisions/EM output
  integer, parameter:: fileID_boundaryParticle = 30 ! Particle boundaries
  integer, parameter:: fileID_boundaryEM = 31       ! EM boundaries
  integer, parameter:: fileID_inject = 32           ! Injects
  integer, parameter:: fileID_reference = 40        ! Reference values
  integer, parameter:: fileID_time =50              ! Computation time

  !Output for each node 
  TYPE, PUBLIC:: outputNode
    REAL(8):: den = 0.D0, mom(1:3) = 0.D0, tensorS(1:3,1:3) = 0.D0
    CONTAINS
      PROCEDURE, PASS(self), PRIVATE:: outputNode_equal_outputNode
      PROCEDURE, PASS(self), PRIVATE:: outputNode_equal_real
      PROCEDURE, PASS(self), PRIVATE:: outputNode_add_outputNode
      PROCEDURE, PASS(self), PRIVATE:: outputNode_sub_outputNode
      PROCEDURE, PASS(self), PRIVATE:: outputNode_mul_outputNode
      PROCEDURE, PASS(self), PRIVATE:: outputNode_div_int
      GENERIC, PUBLIC :: ASSIGNMENT(=) => outputNode_equal_outputNode, outputNode_equal_real
      GENERIC, PUBLIC :: OPERATOR(+)   => outputNode_add_outputNode
      GENERIC, PUBLIC :: OPERATOR(-)   => outputNode_sub_outputNode
      GENERIC, PUBLIC :: OPERATOR(*)   => outputNode_mul_outputNode
      GENERIC, PUBLIC :: OPERATOR(/)   => outputNode_div_int

  END TYPE

  !Type for EM data in node
  TYPE emNode
    REAL(8):: phi
    REAL(8):: B(1:3)

  END TYPE emNode

  !Output in dimensional units to print
  TYPE outputFormat
    REAL(8):: density, velocity(1:3), pressure, temperature

  END TYPE

  CONTAINS
    PURE FUNCTION formatFileName(pref, suff, extension, timeStep) RESULT(fileName)
      IMPLICIT NONE

      CHARACTER(*), INTENT(in):: pref, suff, extension
      INTEGER, INTENT(in), OPTIONAL:: timeStep
      CHARACTER (LEN=iterationDigits):: tString
      CHARACTER(:), ALLOCATABLE:: fileName

      IF (PRESENT(timeStep)) THEN
        WRITE(tString, iterationFormat) timeStep
        fileName = pref //  '_' // tString // '_' // suff // '.' // extension

      ELSE
        fileName = pref //  '_' // suff // '.' // extension

      END IF

    END FUNCTION formatFileName

    subroutine createOutputFolder()
      implicit none

      call execute_command_line('mkdir ' // path // folder )

    end subroutine createOutputFolder

    subroutine copyFileToOutput(fileName)
      implicit none

      character(*), intent(in):: fileName

      call execute_command_line('cp ' // fileName // ' ' // path // folder)

    end subroutine copyFileToOutput

    subroutine writeCommit()
      implicit none

      call system('git rev-parse HEAD > ' // path // folder // '/' // 'fpakc_commit.txt')

    end subroutine writeCommit

    pure function generateFilePath(filename) result(completePath)
      implicit none

      character(*), intent(in):: fileName
      character(:), allocatable:: completePath

      completePath = path // folder // '/' // fileName

    end function generateFilePath

    subroutine informFileCreation(filename)
      implicit none

      character(*), intent(in):: fileName

      write(*, "(6X,A15,A)") "Creating file: ", fileName

    end subroutine informFileCreation

    PURE SUBROUTINE outputNode_equal_outputNode(self, from)
      IMPLICIT NONE

      CLASS(outputNode), INTENT(inout):: self
      CLASS(outputNode), INTENT(in):: from

      self%den     = from%den
      self%mom     = from%mom
      self%tensorS = from%tensorS

    END SUBROUTINE outputNode_equal_outputNode

    PURE ELEMENTAL SUBROUTINE outputNode_equal_real(self, from)
      IMPLICIT NONE

      CLASS(outputNode), INTENT(inout):: self
      REAL(8), INTENT(in):: from

      self%den     = from
      self%mom     = from
      self%tensorS = from

    END SUBROUTINE outputNode_equal_real

    PURE ELEMENTAL FUNCTION outputNode_add_outputNode(self, that) RESULT(total)
      IMPLICIT NONE

      CLASS(outputNode), INTENT(in):: self
      CLASS(outputNode), INTENT(in):: that
      TYPE(outputNode):: total

      total%den     = self%den     + that%den
      total%mom     = self%mom     + that%mom
      total%tensorS = self%tensorS + that%tensorS

    END FUNCTION outputNode_add_outputNode

    PURE ELEMENTAL FUNCTION outputNode_sub_outputNode(self, that) RESULT(total)
      IMPLICIT NONE

      CLASS(outputNode), INTENT(in):: self
      CLASS(outputNode), INTENT(in):: that
      TYPE(outputNode):: total

      total%den     = self%den     - that%den
      total%mom     = self%mom     - that%mom
      total%tensorS = self%tensorS - that%tensorS

    END FUNCTION outputNode_sub_outputNode

    PURE ELEMENTAL FUNCTION outputNode_mul_outputNode(self, that) RESULT(total)
      IMPLICIT NONE

      CLASS(outputNode), INTENT(in):: self
      CLASS(outputNode), INTENT(in):: that
      TYPE(outputNode):: total

      total%den     = self%den     * that%den
      total%mom     = self%mom     * that%mom
      total%tensorS = self%tensorS * that%tensorS

    END FUNCTION outputNode_mul_outputNode

    PURE ELEMENTAL FUNCTION outputNode_div_int(self, that) RESULT(total)
      IMPLICIT NONE

      CLASS(outputNode), INTENT(in):: self
      INTEGER, INTENT(in):: that
      TYPE(outputNode):: total

      total%den     = self%den     / REAL(that)
      total%mom     = self%mom     / REAL(that)
      total%tensorS = self%tensorS / REAL(that)

    END FUNCTION outputNode_div_int

    SUBROUTINE calculateOutput(rawValues, formatValues, nodeVol, speciesIn)
      USE moduleConstParam
      USE moduleRefParam
      USE moduleSpecies
      USE moduleMath
      IMPLICIT NONE

      TYPE(outputNode),   INTENT(in)::  rawValues
      TYPE(outputFormat), INTENT(out):: formatValues
      REAL(8), INTENT(in):: nodeVol
      CLASS(speciesGeneric), INTENT(in):: speciesIn
      REAL(8), DIMENSION(1:3,1:3):: tensorTemp
      REAL(8), DIMENSION(1:3):: tempVel
      REAL(8):: tempVol

      !Resets the node outputs
      formatValues%density     = 0.D0
      formatValues%velocity    = 0.D0
      formatValues%pressure    = 0.D0
      formatValues%temperature = 0.D0
      tempVol = 1.D0/(nodeVol*Vol_ref)
      IF (rawValues%den > 0.D0) THEN
        tempVel    =  rawValues%mom(:)/rawValues%den
        tensorTemp = (rawValues%tensorS(:,:) - rawValues%den*outerProduct(tempVel,tempVel))
        formatValues%density     = rawValues%den*tempVol
        formatValues%velocity(:) = tempVel
        IF (tensorTrace(tensorTemp) > 0.D0) THEN
         formatValues%pressure    = speciesIn%m*tensorTrace(tensorTemp)*tempVol/3.D0
         formatValues%temperature = formatValues%pressure/(formatValues%density*kb)

        END IF
      END IF

      formatValues%velocity    = formatValues%velocity*v_ref
      formatValues%pressure    = formatValues%pressure*m_ref*v_ref**2
      formatValues%temperature = formatValues%temperature*m_ref*v_ref**2

    END SUBROUTINE calculateOutput

    SUBROUTINE writeTime(first)
      USE moduleSpecies
      USE moduleCompTime
      USE moduleCaseParam, ONLY: timeStep
      IMPLICIT NONE

      LOGICAL, INTENT(in), OPTIONAL:: first
      CHARACTER(:), ALLOCATABLE:: fileName

      fileName = 'cpuTime.csv'

      IF (timeOutput) THEN
        IF (PRESENT(first)) THEN
          IF (first) THEN
            OPEN(fileID_time, file = generateFilePath(fileName), action = 'write')
            WRITE(fileID_time, "(*("//fmtColStr//"))") "t","n","total (s)","push (s)","reset (s)",        &
                                                               "collision (s)","coulomb (s)", &
                                                               "weighting (s)","EMField (s)"
            call informFileCreation(fileName)
            CLOSE(fileID_time)

          END IF

        END IF

        OPEN(fileID_time, file = generateFilePath(fileName), position = 'append', action = 'write')

        WRITE (fileID_time, "(*("//fmtColInt//"),*("//fmtColReal//"))") timeStep, nPartOld, tStep, tPush, tReset, tColl, tCoul, tWeight, tEMField

        CLOSE(fileID_time)

      END IF

    END SUBROUTINE writeTime

    ! Write file with reference values
    subroutine writeReference()
      use moduleRefParam
      implicit none

      open (fileID_reference, file=generateFilePath('reference.csv'))
      write(fileID_reference, "(*("//fmtColStr//"))") '"L_ref (m)"',       &
                                                      '"v_ref (m s^-1)"',  &
                                                      '"ti_ref (s)"',      &
                                                      '"Vol_ref (m^3)"',   &
                                                      '"EF_ref (V m^-1)"', &
                                                      '"Volt_ref (V)"',    &
                                                      '"B_ref (T)"'
      write(fileID_reference, "(*("//fmtColReal//"))")  L_ref,    &
                                                        v_ref,    &
                                                        ti_ref,   &
                                                        Vol_ref,  &
                                                        EF_ref,   &
                                                        Volt_ref, &
                                                        B_ref
      close(fileID_reference)

    end subroutine writeReference

END MODULE moduleOutput

