// Copyright (C) 2012  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_IOSOCKSTrEAM_Hh_
#define DLIB_IOSOCKSTrEAM_Hh_

#include "iosockstream_abstract.h"

#include <iostream>
#include <memory>

#include "../sockstreambuf.h"
#include "../timeout.h"

#ifdef _MSC_VER
// Disable the warning about inheriting from std::iostream 'via dominance' since this warning is a warning about
// visual studio conforming to the standard and is ignorable.  
// See http://connect.microsoft.com/VisualStudio/feedback/details/733720/inheriting-from-std-fstream-produces-c4250-warning
// for further details if interested.
#pragma warning(disable : 4250)
#endif // _MSC_VER

namespace dlib
{

// ---------------------------------------------------------------------------------------- 

    class iosockstream : public std::iostream
    {
    public:

        iosockstream(
        ) :
            std::iostream(0)
        {
        }

        iosockstream( 
            const network_address& addr
        ) :
            std::iostream(0)
        { 
            open(addr); 
        }

        iosockstream( 
            const network_address& addr,
            unsigned long timeout 
        ) :
            std::iostream(0)
        { 
            open(addr, timeout); 
        }

        ~iosockstream()
        {
            close();
        }

        void open (
            const network_address& addr
        )
        {
            auto_mutex lock(class_mutex);
            close();
            con.reset(connect(addr));
            buf.reset(new sockstreambuf(con.get()));
            // Note that we use the sockstreambuf's ability to autoflush instead of 
            // telling the iostream::tie() function to tie the stream to itself even though
            // that should work fine.  The reason we do it this way is because there is a
            // bug in visual studio 2012 that causes a program to crash when a stream is
            // tied to itself and then used.  See
            // http://connect.microsoft.com/VisualStudio/feedback/details/772293/tying-a-c-iostream-object-to-itself-causes-a-stack-overflow-in-visual-studio-2012
            // for further details.
            buf->flush_output_on_read();
            rdbuf(buf.get());
            clear();
        }

        void open (
            const network_address& addr,
            unsigned long timeout
        )
        {
            auto_mutex lock(class_mutex);
            close(timeout);
            con.reset(connect(addr.host_address, addr.port, timeout));
            buf.reset(new sockstreambuf(con.get()));
            buf->flush_output_on_read();
            rdbuf(buf.get());
            clear();
        }

        void close(
            unsigned long timeout = 10000
        )
        {
            auto_mutex lock(class_mutex);
            rdbuf(0);
            try
            {
                if (buf)
                {
                    dlib::timeout t(*con,&connection::shutdown,timeout);

                    // This will flush the sockstreambuf and also destroy it.
                    buf.reset();

                    if(con->shutdown_outgoing())
                    {
                        // there was an error so just close it now and return
                        con->shutdown();
                    }
                    else
                    {
                        char junk[100];
                        // wait for the other end to close their side
                        while (con->read(junk,sizeof(junk)) > 0);
                    }
                }
            }
            catch (...)
            {
                con.reset();
                throw;
            }
            con.reset();
        }

        void terminate_connection_after_timeout (
            unsigned long timeout
        )
        {
            auto_mutex lock(class_mutex);
            if (con)
            {
                con_timeout.reset(new dlib::timeout(*this,&iosockstream::terminate_connection,timeout,con));
            }
        }

        void shutdown (
        ) 
        {
            auto_mutex lock(class_mutex);
            if (con)
                con->shutdown();
        }

    private:

        void terminate_connection(
            std::shared_ptr<connection> thecon
        )
        {
            thecon->shutdown();
        }

        std::unique_ptr<timeout> con_timeout;
        rmutex class_mutex; 
        std::shared_ptr<connection> con;
        std::unique_ptr<sockstreambuf> buf;

    };

// ---------------------------------------------------------------------------------------- 

}


#endif // DLIB_IOSOCKSTrEAM_Hh_