//#####################################################################
// Copyright 2004-2007, Eran Guendelman, Geoffrey Irving, Igor Neverov, Andrew Selle, Eftychios Sifakis.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class Read_Write
//#####################################################################
// Functions for reading and writing which do the correct thing for objects, pointers, primitive types, etc. In general, use Read/Write_Binary (and Read/Write_Binary_Array) using T for the type
// of the object you're reading/writing and RW the underlying floating point scalar type (float/double).
//#####################################################################
#ifndef __READ_WRITE__
#define __READ_WRITE__

#include <Core/Read_Write/READ_WRITE_FORWARD.h>
#include <Core/Read_Write/TYPED_STREAM.h>
#include <Core/Utilities/TYPE_UTILITIES.h>
#include <Core/Vectors/SCALAR_POLICY.h>
#include <cassert>
#include <iostream>
#include <utility>

using std::pair;
using std::tuple;
using std::is_same;
namespace PhysBAM{

// classify types which can be written directly to files without conversion
template<class T,class RW,class ENABLER> struct IS_BINARY_IO_SAFE {static const bool value=false;};

template<class R,class... Args>
std::ostream& operator<<(std::ostream& output_stream,R (*func)(Args...))
{output_stream<<(void*)func;return output_stream;}

template<class T>
struct HAS_UNTYPED_READ
{
    template<class A> static auto f(A* d) -> enable_if_t<sizeof((d->Read(std::declval<UNTYPED_ISTREAM<float>&>()),1))!=0,char>;
    static int f(const void*);
    static constexpr bool value=(sizeof(f((T*)nullptr))==1);
};

template<class T>
struct HAS_TYPED_READ
{
    template<class A> static auto f(A* d) -> enable_if_t<sizeof((d->Read(std::declval<TYPED_ISTREAM&>()),1))!=0,char>;
    static int f(const void*);
    static constexpr bool value=(sizeof(f((T*)nullptr))==1);
};

template<class T>
struct HAS_UNTYPED_WRITE
{
    template<class A> static auto f(A* d) -> enable_if_t<sizeof((d->Write(std::declval<UNTYPED_OSTREAM<float>&>()),1))!=0,char>;
    static int f(const void*);
    static constexpr bool value=(sizeof(f((T*)nullptr))==1);
};

template<class T>
struct HAS_TYPED_WRITE
{
    template<class A> static auto f(A* d) -> enable_if_t<sizeof((d->Write(std::declval<TYPED_OSTREAM&>()),1))!=0,char>;
    static int f(const void*);
    static constexpr bool value=(sizeof(f((T*)nullptr))==1);
};

//#####################################################################
// Read_Binary
//#####################################################################
template<class RW,class T1,class T2,class ...Args>
void Read_Binary(UNTYPED_ISTREAM<RW>& input,T1& d1,T2& d2,Args&& ...args);

template<class RW,class T> std::enable_if_t<std::is_arithmetic<T>::value&&!std::is_const<T>::value>
Read_Binary(UNTYPED_ISTREAM<RW>& input,T& d);

template<class RW,class T> std::enable_if_t<std::is_enum<T>::value&&!std::is_const<T>::value>
Read_Binary(UNTYPED_ISTREAM<RW>& input,T& d)
{int x;Read_Binary(input,x);d=(T)x;}

template<class RW,class T> std::enable_if_t<HAS_UNTYPED_READ<T>::value&&!std::is_const<T>::value>
Read_Binary(UNTYPED_ISTREAM<RW>& input,T& d)
{
    if constexpr(!is_same<RW,char>::value && IS_BINARY_IO_SAFE<T,RW>::value)
        input.read(reinterpret_cast<char*>(&d),sizeof(T));
    else d.Read(input);
}

template<class RW,class T> std::enable_if_t<HAS_TYPED_READ<T>::value&&!std::is_const<T>::value>
Read_Binary(UNTYPED_ISTREAM<RW>& input,T& d)
{
    if constexpr(!is_same<RW,char>::value && IS_BINARY_IO_SAFE<T,RW>::value)
        input.read(reinterpret_cast<char*>(&d),sizeof(T));
    else
    {
        TYPED_ISTREAM typed_input(input,Stream_Type(RW()));
        d.Read(typed_input);
    }
}

template<class RW,class T> void
Read_Binary(UNTYPED_ISTREAM<RW>& input,std::complex<T>& d)
{
    T r,c;
    Read_Binary(input,r,c);
    d=std::complex<T>(r,c);
}

template<class RW> void
Read_Binary(UNTYPED_ISTREAM<RW>& input,std::string& d);

template<class RW,class T> void
Read_Binary(UNTYPED_ISTREAM<RW>& input,T*& d)
{
    if constexpr(is_same<RW,char>::value)
    {
        if(Try_Get(input,'('))
        {
            d=new T();
            Read_Binary(input,*d);
            Get(input,')');
        }
        else
        {
            int i;
            input.raw.stream>>i;
            assert(i==0);
            d=nullptr;
        }
    }
    else
    {
        bool data_exists;
        Read_Binary(input,data_exists);
        if(data_exists)
        {
            d=new T();
            Read_Binary(input,*d);
        }
        else d=nullptr;
    }
}

template<class RW,class A,class B> void
Read_Binary(UNTYPED_ISTREAM<RW>& input,pair<A,B>& p)
{Get(input,'(');Read_Binary(input,p.first,p.second);Get(input,')');}

template<class RW,class... Args> void
Read_Binary(UNTYPED_ISTREAM<RW>& input,tuple<Args...>& t)
{std::apply([&input](Args&... args){Read_Binary(input,args...);}, t);}

template<class RW,class T>
void Read_Binary_TU(TYPED_ISTREAM& t,UNTYPED_ISTREAM<RW>& u,T& d)
{
    if constexpr(HAS_TYPED_READ<T>::value)
        d.Read(t);
    else Read_Binary(u,d);
}
template<class ...Args>
void Read_Binary(TYPED_ISTREAM& input,Args&& ...args)
{
    if(input.type==STREAM_TYPE::type_double)
    {
        UNTYPED_ISTREAM<double> s(input.raw);
        (Read_Binary_TU(input,s,args),...);
    }
    else if(input.type==STREAM_TYPE::type_float)
    {
        UNTYPED_ISTREAM<float> s(input.raw);
        (Read_Binary_TU(input,s,args),...);
    }
    else
    {
        UNTYPED_ISTREAM<char> s(input.raw);
        (Read_Binary_TU(input,s,args),...);
    }
}

//#####################################################################
// Write_Binary
//#####################################################################
template<class RW,class T1,class T2,class ...Args>
void Write_Binary(UNTYPED_OSTREAM<RW>& output,const T1& d1,const T2& d2,Args&& ...args);

template<class RW,class T> std::enable_if_t<std::is_arithmetic<T>::value>
Write_Binary(UNTYPED_OSTREAM<RW>& output,const T& d);

template<class RW,class T> std::enable_if_t<std::is_enum<T>::value>
Write_Binary(UNTYPED_OSTREAM<RW>& output,const T& d)
{Write_Binary(output,(int)d);}

template<class RW,class T> std::enable_if_t<HAS_UNTYPED_WRITE<T>::value>
Write_Binary(UNTYPED_OSTREAM<RW>& output,const T& d)
{
    if constexpr(!is_same<RW,char>::value && IS_BINARY_IO_SAFE<T,RW>::value)
        output.write(reinterpret_cast<const char*>(&d),sizeof(T));
    else d.Write(output);
}

template<class RW,class T> std::enable_if_t<HAS_TYPED_WRITE<T>::value>
Write_Binary(UNTYPED_OSTREAM<RW>& output,const T& d)
{
    if constexpr(!is_same<RW,char>::value && IS_BINARY_IO_SAFE<T,RW>::value)
        output.write(reinterpret_cast<const char*>(&d),sizeof(T));
    else
    {
        TYPED_OSTREAM typed_output(output,Stream_Type(RW()));
        d.Write(typed_output);
    }
}

template<class RW,class T> void
Write_Binary(UNTYPED_OSTREAM<RW>& output,const std::complex<T>& d)
{
    Write_Binary(output,d.real(),d.imag());
}

template<class RW> void
Write_Binary(UNTYPED_OSTREAM<RW>& output,const std::string& d);

template<class RW,class T> void
Write_Binary(UNTYPED_OSTREAM<RW>& output,const T* d)
{
    if constexpr(is_same<RW,char>::value)
    {
        if(d)
        {
            Beg(output,'<');
            Write_Binary(output,*d);
            End(output,'>');
        }
        else
        {
            if(output.raw.need_space) output.raw.stream<<' ';
            output.raw.stream<<"0";
            output.raw.need_space=true;
        }
    }
    else
    {
        Write_Binary(output,d!=0);
        if(d) Write_Binary(output,*d);
    }
}

template<class RW,class A,class B> void
Write_Binary(UNTYPED_OSTREAM<RW>& output,const pair<A,B>& p)
{
    Beg(output,'(');
    Write_Binary(output,p.first,p.second);
    End(output,')');
}

template<class RW,class... Args> void
Write_Binary(UNTYPED_OSTREAM<RW>& output,const tuple<Args...>& t)
{
    Beg(output,'(');
    std::apply([&output](const Args&... args){Write_Binary(output,args...);}, t);
    End(output,')');
}
template<class RW,class T>
void Write_Binary_TU(TYPED_OSTREAM& t,UNTYPED_OSTREAM<RW>& u,const T& d)
{
    if constexpr(HAS_TYPED_WRITE<T>::value)
        d.Write(t);
    else Write_Binary(u,d);
}
template<class ...Args>
void Write_Binary(TYPED_OSTREAM& output,Args&& ...args)
{
    if(output.type==STREAM_TYPE::type_double)
    {
        UNTYPED_OSTREAM<double> s(output.raw);
        (Write_Binary_TU(output,s,args),...);
    }
    else if(output.type==STREAM_TYPE::type_float)
    {
        UNTYPED_OSTREAM<float> s(output.raw);
        (Write_Binary_TU(output,s,args),...);
    }
    else
    {
        UNTYPED_OSTREAM<char> s(output.raw);
        (Write_Binary_TU(output,s,args),...);
    }
}

//#####################################################################
// Multiple Argument Write_Binary
//#####################################################################
template<class RW,class T1,class T2,class ...Args>
void Read_Binary(UNTYPED_ISTREAM<RW>& input,T1& d1,T2& d2,Args&& ...args)
{
    Read_Binary(input,d1);
    Read_Binary(input,d2);
    (Read_Binary(input,args),...);
}
//#####################################################################
// Multiple Argument Write_Binary
//#####################################################################
template<class RW,class T1,class T2,class ...Args>
void Write_Binary(UNTYPED_OSTREAM<RW>& output,const T1& d1,const T2& d2,Args&& ...args)
{
    Write_Binary(output,d1);
    Write_Binary(output,d2);
    (Write_Binary(output,args),...);
}

//#####################################################################
// Multiple Argument Read_Binary_Group
//#####################################################################
template<class S,class ...Args>
void Read_Binary_Group(S& input,Args&& ...args)
{
    Get(input,'(');
    Read_Binary(input,args...);
    Get(input,')');
}
//#####################################################################
// Multiple Argument Write_Binary_Group
//#####################################################################
template<class S,class ...Args>
void Write_Binary_Group(S& output,Args&& ...args)
{
    Beg(output,'(');
    Write_Binary(output,args...);
    End(output,')');
}
//#####################################################################
// Read/Write_Binary_Array
//#####################################################################
template<class RW,class T> void
Read_Binary_Array(UNTYPED_ISTREAM<RW>& input,T* array,int number_of_elements)
{
    if(!number_of_elements) return;
    if constexpr(!is_same<RW,char>::value && IS_BINARY_IO_SAFE<T,RW>::value)
        input.read(reinterpret_cast<char*>(array),number_of_elements*sizeof(T));
    else
    {
        for(int i=0;i<number_of_elements;i++)
            Read_Binary(input,array[i]);
    }
}
//#####################################################################
// Function Read_Binary_Array
//#####################################################################
template<class T> void
Read_Binary_Array(TYPED_ISTREAM& input,T* array,int number_of_elements)
{
    if(input.type==STREAM_TYPE::type_double)
    {
        UNTYPED_ISTREAM<double> s(input.raw);
        Read_Binary_Array(s,array,number_of_elements);
    }
    else if(input.type==STREAM_TYPE::type_float)
    {
        UNTYPED_ISTREAM<float> s(input.raw);
        Read_Binary_Array(s,array,number_of_elements);
    }
    else
    {
        UNTYPED_ISTREAM<char> s(input.raw);
        Read_Binary_Array(s,array,number_of_elements);
    }
}
//#####################################################################
// Function Write_Binary_Array
//#####################################################################
template<class RW,class T> void
Write_Binary_Array(UNTYPED_OSTREAM<RW>& output,const T* array,int number_of_elements)
{
    if(!number_of_elements) return;
    if constexpr(!is_same<RW,char>::value && IS_BINARY_IO_SAFE<T,RW>::value)
        output.write(reinterpret_cast<const char*>(array),number_of_elements*sizeof(T));
    else
    {
        Write_Binary(output,array[0]);
        for(int i=1;i<number_of_elements;i++)
            Write_Binary(output,array[i]);
    }
}
//#####################################################################
// Function Write_Binary_Array
//#####################################################################
template<class T> void
Write_Binary_Array(TYPED_OSTREAM& output,const T* array,int number_of_elements)
{
    if(output.type==STREAM_TYPE::type_double)
    {
        UNTYPED_OSTREAM<double> s(output.raw);
        Write_Binary_Array(s,array,number_of_elements);
    }
    else if(output.type==STREAM_TYPE::type_float)
    {
        UNTYPED_OSTREAM<float> s(output.raw);
        Write_Binary_Array(s,array,number_of_elements);
    }
    else
    {
        UNTYPED_OSTREAM<char> s(output.raw);
        Write_Binary_Array(s,array,number_of_elements);
    }
}
//#####################################################################
// Multiple Argument Read_Binary_Group
//#####################################################################
template<class S,class T>
void Read_Binary_Array_Group(S& input,T* array,int number_of_elements)
{
    Get(input,'(');
    Read_Binary_Array(input,array,number_of_elements);
    Get(input,')');
}
//#####################################################################
// Multiple Argument Write_Binary_Group
//#####################################################################
template<class S,class T>
void Write_Binary_Array_Group(S& output,const T* array,int number_of_elements)
{
    Beg(output,'(');
    Write_Binary_Array(output,array,number_of_elements);
    End(output,')');
}

template<class RW> bool Test(UNTYPED_ISTREAM<RW>& input,char a){return true;}
template<class RW> bool Try_Get(UNTYPED_ISTREAM<RW>& input,char a){return true;}
template<class RW> void Get(UNTYPED_ISTREAM<RW>& input,char a){}
template<class RW> void Beg(UNTYPED_OSTREAM<RW>& output,char a){}
template<class RW> void Sep(UNTYPED_OSTREAM<RW>& output,char a,bool space=true){}
template<class RW> void End(UNTYPED_OSTREAM<RW>& output,char a){}
bool Test(UNTYPED_ISTREAM<char>& input,char a);
bool Try_Get(UNTYPED_ISTREAM<char>& input,char a);
void Get(UNTYPED_ISTREAM<char>& input,char a);
void Beg(UNTYPED_OSTREAM<char>& output,char a);
void Sep(UNTYPED_OSTREAM<char>& output,char a,bool space=true);
inline void End(UNTYPED_OSTREAM<char>& output,char a){return Sep(output,a,true);}
bool Test(TYPED_ISTREAM& input,char a);
bool Try_Get(TYPED_ISTREAM& input,char a);
void Get(TYPED_ISTREAM& input,char a);
void Beg(TYPED_OSTREAM& output,char a);
void Sep(TYPED_OSTREAM& output,char a,bool space=true);
inline void End(TYPED_OSTREAM& output,char a){Sep(output,a,true);}

//#####################################################################
// Function Swap_Endianity
//#####################################################################
template<class T>
inline void Swap_Endianity(T& x)
{assert(sizeof(T)<=8);
if(sizeof(T)>1) {T old=x;for(unsigned int k=0;k<sizeof(T);k++) ((char*)&x)[k]=((char*)&old)[sizeof(T)-k-1];}}
}
#include <Core/Data_Structures/TUPLE.h>
#endif
