module AnalystClass
    use uuid_module
    use IOClass
    use ListClass
    use MathClass
    use ArrayClass
    implicit none
    
    type :: PDF_
        character(:),allocatable :: scene_name       
        logical :: confidential = .false. 
    end type

    interface operator(//)
        module procedure joint_pdfs_and_pdfs
    end interface


    type :: Plot_
        real(real64),allocatable :: x_list(:)
        real(real64),allocatable :: y_list(:,:)
        character(:),allocatable :: x_label
        character(:),allocatable :: y_label
        character(:),allocatable :: title 
        logical :: with_line = .false.
        logical :: logscale = .false.  
        logical :: x_logscale = .false.  
        logical :: y_logscale = .false.  
        logical :: z_logscale = .false.  
        real(real64),allocatable :: x_range(:)
        real(real64),allocatable :: y_range(:)
        real(real64),allocatable :: z_range(:)
        type(List_) :: regend
    end type


    type :: Analyst_

    contains
        procedure,pass :: to_pdf_from_plots_analystclass
        procedure,pass :: to_pdf_from_plots_auto_div_analystclass
        generic,public :: to_pdf =>&
            to_pdf_from_plots_analystclass,&
            to_pdf_from_plots_auto_div_analystclass
        procedure,public :: render => render_analystclass


        procedure,pass :: to_plot_analystclass_vec_vec
        procedure,pass :: to_plot_analystclass_vec_array
        generic :: to_plot => to_plot_analystclass_vec_vec,&
            to_plot_analystclass_vec_array

        procedure,pass :: pdf_from_plots_analystclass
        procedure,pass :: pdf_analystclass_vec_vec
        procedure,pass :: pdf_analystclass_vec_array
        generic :: pdf => pdf_analystclass_vec_vec,&
            pdf_analystclass_vec_array,&
            pdf_from_plots_analystclass
    end type

contains

! ###################################################
function to_plot_analystclass_vec_vec(this,x_list,y_list,x_label,y_label,&
    title,with_line,logscale,x_logscale,y_logscale,z_logscale,x_range,y_range,&
    z_range,regend) result(ret)
    class(Analyst_),intent(in) :: this
    type(Plot_) :: ret
    real(real64),intent(in) :: x_list(:),y_list(:)
    character(*),intent(in) :: x_label,y_label,title
    type(List_),optional,intent(in) :: regend
    logical,intent(in) :: with_line

    logical,optional,intent(in) :: logscale,x_logscale,y_logscale,z_logscale
    real(real64),optional,intent(in) :: x_range(1:2),y_range(1:2),z_range(1:2)

    ret % x_list    = x_list

    ret % y_list    = zeros(size(y_list),1 )
    ret % y_list(:,1)    = y_list(:)
    
    ret % x_label   = x_label
    ret % y_label   = y_label
    ret % title     = title
    ret % with_line = with_line
    if(present(regend) )then
        ret % regend = regend
    endif

    if(present(logscale) )then
        ret % logscale  = logscale
    endif

    if(present(x_logscale) )then
        ret % x_logscale  = x_logscale
    endif

    if(present(y_logscale) )then
        ret % y_logscale  = y_logscale
    endif

    if(present(z_logscale) )then
        ret % z_logscale  = z_logscale
    endif

    if(present(x_range) )then
        ret % x_range = x_range
    endif


    if(present(y_range) )then
        ret % y_range = y_range
    endif

    if(present(z_range) )then
        ret % z_range = z_range
    endif
end function
! ###################################################


! ###################################################
function to_plot_analystclass_vec_array(this,x_list,y_list,x_label,y_label,&
    title,with_line,logscale,x_logscale,y_logscale,z_logscale,x_range,y_range,&
    z_range,regend) result(ret)
    class(Analyst_),intent(in) :: this
    type(Plot_) :: ret
    real(real64),intent(in) :: x_list(:),y_list(:,:)
    character(*),intent(in) :: x_label,y_label,title
    type(List_),optional,intent(in) :: regend
    logical,intent(in) :: with_line


    logical,optional,intent(in) :: logscale,x_logscale,y_logscale,z_logscale
    real(real64),optional,intent(in) :: x_range(1:2),y_range(1:2),z_range(1:2)

    ret % x_list    = x_list

    ret % y_list    = y_list
    
    ret % x_label   = x_label
    ret % y_label   = y_label
    ret % title     = title
    ret % with_line = with_line
    if(present(regend) )then
        ret % regend = regend
    endif
    
    if(present(logscale) )then
        ret % logscale  = logscale
    endif

    if(present(x_logscale) )then
        ret % x_logscale  = x_logscale
    endif

    if(present(y_logscale) )then
        ret % y_logscale  = y_logscale
    endif

    if(present(z_logscale) )then
        ret % z_logscale  = z_logscale
    endif

    if(present(x_range) )then
        ret % x_range = x_range
    endif


    if(present(y_range) )then
        ret % y_range = y_range
    endif

    if(present(z_range) )then
        ret % z_range = z_range
    endif

    
end function
! ###################################################


! ###################################################
subroutine pdf_analystclass_vec_vec(this,name,x_list,y_list,x_label,y_label,&
    title,with_line,logscale,x_logscale,y_logscale,z_logscale,regend)
    class(Analyst_),intent(in) :: this
    real(real64),intent(in) :: x_list(:),y_list(:)
    character(*),intent(in) :: name,x_label,y_label,title
    logical,intent(in) :: with_line
    type(List_),optional,intent(in) :: regend
    logical,optional,intent(in) :: logscale,x_logscale,y_logscale,z_logscale
    integer(int32) :: system_ret
    character(:),allocatable :: wl
    type(IO_) :: f
    integer(int32) :: i

    if(with_line)then
        wl = "w l "
    else
        wl = " "
    endif
    
    call f%open(name+".csv","w")
    do i = 1,min(size(x_list),size(y_list) )
        write(f%fh,*) x_list(i),",",y_list(i)
    enddo
    call f%close()


    call f%open(name+".gp","w")
    
    
    call f%write("set terminal pdf")
    call f%write("set output '"+name+"'")
    call f%write("set datafile separator ','")
    call f%write("set grid")
    call f%write('set key font "Times, 14"')

    if(.not.present(regend) )then
        call f%write("unset key")
    endif
    call f%write("set xtics font 'Times, 14'")
    call f%write("set ytics font 'Times, 14'")
    if(present(logscale) )then
        if(logscale)then
            call f%write("set logscale")
        endif
    endif
    if(present(x_logscale) )then
        if(x_logscale)then
            call f%write("set logscale x")
        endif
    endif

    if(present(y_logscale) )then
        if(y_logscale)then
            call f%write("set logscale y")
        endif
    endif

    if(present(z_logscale) )then
        if(z_logscale)then
            call f%write("set logscale z")
        endif
    endif

    call f%write("set xlabel '"+x_label+"'  font 'Times, 14'")
    call f%write("set ylabel '"+y_label+"'  font 'Times, 14'")
    call f%write("set title '"+title+"' font 'Times, 14'")
    

    if(present(regend) )then
        call f%write("plot '"+name+".csv' u 1:2 "+wl+" title '"+regend%get(1)+"'" )
    else
        call f%write("plot '"+name+".csv' u 1:2 "+wl)
    endif
    
    
    call f%write("exit")
    call f%close()

    system_ret = system("gnuplot "+name+".gp")
    

end subroutine
! ###################################################

! ###################################################
subroutine pdf_analystclass_vec_array(this,name,x_list,y_list,x_label,y_label,&
    title,with_line,logscale,x_logscale,y_logscale,z_logscale,regend)
    class(Analyst_),intent(in) :: this
    real(real64),intent(in) :: x_list(:),y_list(:,:)
    character(*),intent(in) :: name,x_label,y_label,title
    logical,intent(in) :: with_line
    type(List_),intent(in) :: regend
    logical,optional,intent(in) :: logscale,x_logscale,y_logscale,z_logscale
    character(:),allocatable :: command
    character(:),allocatable :: wl
    type(IO_) :: f
    integer(int32) :: i,system_ret

    if(with_line)then
        wl = "w l "
    else
        wl = " "
    endif
    
    call f%open(name+".tsv","w")
    do i = 1,min(size(x_list),size(y_list,1) )
        write(f%fh,*) x_list(i),y_list(i,:)
    enddo
    call f%close()


    call f%open(name+".gp","w")
    call f%write("set terminal pdf ")
    call f%write("set output '"+name+"'")
    
    call f%write("set grid")
    call f%write('set key font "Times, 14"')
    !call f%write("unset key")
    call f%write("set xtics font 'Times, 14'")
    call f%write("set ytics font 'Times, 14'")

    if(present(logscale) )then
        if(logscale)then
            call f%write("set logscale")
        endif
    endif
    if(present(x_logscale) )then
        if(x_logscale)then
            call f%write("set logscale x")
        endif
    endif

    if(present(y_logscale) )then
        if(y_logscale)then
            call f%write("set logscale y")
        endif
    endif

    if(present(z_logscale) )then
        if(z_logscale)then
            call f%write("set logscale z")
        endif
    endif

    

    call f%write("set xlabel '"+x_label+"'  font 'Times, 14' ")
    call f%write("set ylabel '"+y_label+"'  font 'Times, 14' ")
    call f%write("set title '"+title+"' font 'Times, 14'")
    call f%write("set multiplot")
    
    command = "plot '"+name+".tsv' u 1:2 "+wl+" title '"+regend%get(1)+"'"
    do i=2,size(y_list,2)
        command = command + " , " + " '"+name+".tsv' u 1:"+str(i+1)+" "+wl+" title '"+regend%get(i)+"'"
    enddo

    call f%write(command)
    
    call f%write("unset multiplot")
    
    call f%close()

    system_ret = system("gnuplot "+name+".gp")
    

end subroutine
! ###################################################

! ###################################################
subroutine pdf_from_plots_analystclass(this,name,plot)
    class(Analyst_),intent(in) :: this
    character(*),intent(in) :: name
    type(Plot_),intent(in) :: plot(:,:)
    character(:),allocatable :: command
    type(IO_) :: f, gp
    integer(int32) :: i,j,itr,system_ret
    character(:),allocatable :: wl

    ! write data
    do i=1,size(plot,1)
        do j=1,size(plot,2)
            call f%open(name+zfill(i,4)+zfill(j,4)+".tsv","w")
            do itr = 1,min(size(plot(i,j)%x_list),size(plot(i,j)%y_list,1) )
                write(f%fh,*) plot(i,j)%x_list(itr),plot(i,j)%y_list(itr,:)
            enddo
            call f%close()
        enddo
    enddo

    

    call gp%open(name+".gp","w")
    call gp%write("set terminal pdf size 29.7cm,21.0cm")
    call gp%write("set encoding utf8")
    call gp%write("set output '"+name+"'")
    
    call gp%write("set grid")
    call gp%write('set key font "Times, 14"')
    !call gp%write("unset key")
    call gp%write("set xtics font 'Times, 14'")
    call gp%write("set ytics font 'Times, 14'")

    
    call gp%write("set multiplot layout "+str(size(plot,1) )+" , "+str(size(plot,2) ) )

    do i=1,size(plot,1)
        do j=1,size(plot,2)    

            if(plot(i,j)%logscale)then
                call gp%write("set logscale")
            endif
            if(plot(i,j)%x_logscale)then
                call gp%write("set logscale x")
            endif
            if(plot(i,j)%y_logscale)then
                call gp%write("set logscale y")
            endif
            if(plot(i,j)%z_logscale)then
                call gp%write("set logscale z")
            endif
        

        
            call gp%write("set xlabel '"+plot(i,j)%x_label+"'  font 'Times, 14'")
            call gp%write("set ylabel '"+plot(i,j)%y_label+"'  font 'Times, 14'")
            call gp%write("set title '"+plot(i,j)%title+"' font 'Times, 14'")

            if(allocated(plot(i,j)%x_range) )then
                call gp%write("set xr["+str(plot(i,j)%x_range(1) )+":"+str(plot(i,j)%x_range(2) )+"]")
            endif
            if(allocated(plot(i,j)%y_range) )then
                call gp%write("set xr["+str(plot(i,j)%y_range(1) )+":"+str(plot(i,j)%y_range(2) )+"]")
            endif
            if(allocated(plot(i,j)%z_range) )then
                call gp%write("set xr["+str(plot(i,j)%z_range(1) )+":"+str(plot(i,j)%z_range(2) )+"]")
            endif

            if(plot(i,j)%with_line)then
                wl = "w l "
            else
                wl = " "
            endif

            command = "plot '"+name+zfill(i,4)+zfill(j,4)+".tsv' u 1:2 "+wl+" title '"+plot(i,j)%regend%get(1)+"'"
            do itr=2,size(plot(i,j)%y_list,2)
                command = command + " , " + " '"+name+zfill(i,4)+zfill(j,4)+".tsv' u 1:"+str(itr+1)+" "+wl &
                    +" title '"+plot(i,j)%regend%get(itr)+"'"
            enddo
        
            call gp%write(command)
        enddo
    enddo

    call gp%write("unset multiplot")
    call gp%close()
    system_ret= system("gnuplot "+name+".gp")
    


end subroutine
! ###################################################




! ###################################################
function to_pdf_from_plots_analystclass(this,plot,option,layout) result(pdfobj)
    class(Analyst_),intent(in) :: this
    type(Plot_),intent(in) :: plot(:,:)
    character(*),optional,intent(in) :: option
    integer(int32),optional,intent(in) :: layout(1:2)
    type(PDF_) :: pdfobj
    character(:),allocatable :: command
    type(IO_) :: f, gp
    integer(int32) :: i,j,itr
    character(:),allocatable :: wl
    character(:),allocatable :: name

    name = "analystclass_"+generate_uuid(1)

    ! write data
    do i=1,size(plot,1)
        do j=1,size(plot,2)
            call f%open(name+zfill(i,4)+zfill(j,4)+".tsv","w")
            do itr = 1,min(size(plot(i,j)%x_list),size(plot(i,j)%y_list,1) )
                write(f%fh,*) plot(i,j)%x_list(itr),plot(i,j)%y_list(itr,:)
            enddo
            call f%close()
        enddo
    enddo



    call gp%open(name+".gp","w")
    call gp%write("set terminal pdf size 29.7cm,21.0cm")
    call gp%write("set encoding utf8")
    call gp%write("set output '"+name+".pdf'")
    
    call gp%write("set grid")
    call gp%write('set key font "Times, 14"')
    !call gp%write("unset key")
    call gp%write("set xtics font 'Times, 14'")
    call gp%write("set ytics font 'Times, 14'")

    if(present(layout) )then
        call gp%write("set multiplot layout "+str(layout(1) )+" , "+str(layout(2) ) )
    else
        call gp%write("set multiplot layout "+str(size(plot,1) )+" , "+str(size(plot,2) ) )
    endif

    do i=1,size(plot,1)
        do j=1,size(plot,2)    

            if(plot(i,j)%logscale)then
                call gp%write("set logscale")
            endif
            if(plot(i,j)%x_logscale)then
                call gp%write("set logscale x")
            endif
            if(plot(i,j)%y_logscale)then
                call gp%write("set logscale y")
            endif
            if(plot(i,j)%z_logscale)then
                call gp%write("set logscale z")
            endif
        

        
            call gp%write("set xlabel '"+plot(i,j)%x_label+"'  font 'Times, 14'")
            call gp%write("set ylabel '"+plot(i,j)%y_label+"'  font 'Times, 14'")
            call gp%write("set title '"+plot(i,j)%title+"' font 'Times, 14'")


            if(allocated(plot(i,j)%x_range) )then
                call gp%write("set xr["+str(plot(i,j)%x_range(1) )+":"+str(plot(i,j)%x_range(2) )+"]")
            endif
            if(allocated(plot(i,j)%y_range) )then
                call gp%write("set xr["+str(plot(i,j)%y_range(1) )+":"+str(plot(i,j)%y_range(2) )+"]")
            endif
            if(allocated(plot(i,j)%z_range) )then
                call gp%write("set xr["+str(plot(i,j)%z_range(1) )+":"+str(plot(i,j)%z_range(2) )+"]")
            endif

            if(plot(i,j)%with_line)then
                wl = "w l "
            else
                wl = " "
            endif

            command = "plot '"+name+zfill(i,4)+zfill(j,4)+".tsv' u 1:2 "+wl+" title '"+plot(i,j)%regend%get(1)+"'"
            do itr=2,size(plot(i,j)%y_list,2)
                command = command + " , " + " '"+name+zfill(i,4)+zfill(j,4)+".tsv' u 1:"+str(itr+1)+" "+wl &
                    +" title '"+plot(i,j)%regend%get(itr)+"'"
            enddo
        
            call gp%write(command)
        enddo
    enddo

    call gp%write("unset multiplot")
    call gp%close()

    if(present(option) )then
        if(index(option,"onfidential") /=0 .or. index(option,"ONFIDENTIAL")/=0 )then
            if(.not. f%exists("confidential.pdf") )then
                call f%download("https://www.plantfem.org/download/confidential.pdf")
            endif
            if( f%exists("confidential.pdf"))then
                pdfobj%confidential = .true.
            else
                print *, "[WARNING] to_pdf >> watermark 'confidential.pdf' is not &
                    downloaded. Please check internet connection."
            endif
        endif
    endif
    
    
    pdfobj%scene_name = name
    


end function
! ###################################################



! ###################################################
function to_pdf_from_plots_auto_div_analystclass(this,plot,row_per_page,option) result(pdfobj)
    class(Analyst_),intent(in) :: this
    type(Plot_),intent(in) :: plot(:,:)
    integer(int32),intent(in) :: row_per_page
    character(*),optional,intent(in) :: option
    type(PDF_),allocatable :: pdfobj(:)
    integer(int32) :: i, n,from,to

    if(mod(size(plot,1), row_per_page) ==0)then
        n = size(plot,1)/row_per_page
    else
        n = size(plot,1)/row_per_page + 1
    endif

    allocate(pdfobj(n) )
    do i=1,n-1
        from = (i-1) * row_per_page+1
        to   =    i  * row_per_page
        pdfobj(i) =this%to_pdf(plot=plot( from:to,: ),option=option)  
    enddo
    from = (n-1) * row_per_page+1
    pdfobj(n) =this%to_pdf(plot=plot( from:, : ),&
        layout=[row_per_page,size(plot,2) ],&
        option=option)  
    


end function
! ###################################################



! ###################################################
subroutine render_analystclass(this,name,pdf)
    class(Analyst_),intent(in) :: this
    character(*),intent(in) :: name
    type(PDF_),intent(in) :: pdf(:)
    character(:),allocatable :: command
    integer(int32) :: i,system_ret
    
    ! watermark
    do i=1,size(pdf)
        system_ret= system("gnuplot "+pdf(i)%scene_name+".gp")
        if(pdf(i)%confidential )then
            
            command = "pdftk "+pdf(i)%scene_name+".pdf " +" background confidential.pdf output "+pdf(i)%scene_name+"_c.pdf "
            system_ret= system(command)
            command = "mv "+pdf(i)%scene_name+"_c.pdf "+pdf(i)%scene_name+".pdf "
            system_ret= system(command)
        endif
    enddo

    command = "pdfunite "
    do i=1,size(pdf)
        
        command = command + pdf(i)%scene_name+".pdf "
    enddo
    command = command + " "+ name

    

    system_ret= system(command)


end subroutine
! #####################################################

function joint_pdfs_and_pdfs(pdf1,pdf2) result(ret)
    type(PDF_),intent(in) :: pdf1(:),pdf2(:)
    type(PDF_),allocatable :: ret(:)

    allocate(ret( size(pdf1)+size(pdf2) ) )
    ret(1:size(pdf1) )            = pdf1(:)
    ret(size(pdf1)+1:size(pdf1)+size(pdf2) ) = pdf2(:)

end function
! #####################################################

end module
