// Copyright (C) 2003  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_LINKER_KERNEL_1_CPp_
#define DLIB_LINKER_KERNEL_1_CPp_
#include "linker_kernel_1.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    
    linker::
    linker (
    ) :
        running(false),
        running_signaler(running_mutex),
        A(0),
        B(0),
        service_connection_running_signaler(service_connection_running_mutex)
    {
    }

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

    linker::
    linker (
        connection& a,
        connection& b
    ) :
        running(false),
        running_signaler(running_mutex),
        A(0),
        B(0),
        service_connection_running_signaler(service_connection_running_mutex)
    {
        link(a,b);
    }

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

    linker::
    ~linker (
    )
    {
        clear();
    }

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

    void linker::
    clear (
    )
    {

        // shutdown the connections
        cons_mutex.lock();
        if (A != 0 )
        {
            A->shutdown();
            A = 0;
        }
        if (B != 0)
        {
            B->shutdown();
            B = 0;
        }
        cons_mutex.unlock();
       

        // wait for the other threads to signal that they have ended
        running_mutex.lock();
        while (running == true)
        {
            running_signaler.wait();
        }
        running_mutex.unlock();

    }

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

    bool linker::
    is_running (
    ) const
    {
        running_mutex.lock();
        bool temp = running;
        running_mutex.unlock();
        return temp;
    }

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

    void linker::
    link (
        connection& a,
        connection& b
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT( 
            this->is_running() == false ,
            "\tvoid linker::link"
            << "\n\tis_running() == " << this->is_running() 
            << "\n\tthis: " << this
            );

        running_mutex.lock();
        running = true;
        running_mutex.unlock();

        cons_mutex.lock();
        A = &a;
        B = &b;
        cons_mutex.unlock();

        

        service_connection_running_mutex.lock();
        service_connection_running = true;
        service_connection_running_mutex.unlock();

        service_connection_error_mutex.lock();
        service_connection_error = false;
        service_connection_error_mutex.unlock();

        // if we fail to make the thread
        if (!create_new_thread(service_connection,this))
        {
            a.shutdown();
            b.shutdown();

            service_connection_running_mutex.lock();
            service_connection_running = false;
            service_connection_running_mutex.unlock();

            cons_mutex.lock();
            A = 0;
            B = 0;
            cons_mutex.unlock();  

            running_mutex.lock();
            running = false;
            running_mutex.unlock();



            throw dlib::thread_error (
                ECREATE_THREAD,
                "failed to make new thread in linker::link()"
                );
        }



        // forward data from a to b
        char buf[200];
        int status;
        bool error = false; // becomes true if one of the connections returns an error
        while (true)
        {
            status = a.read(buf,sizeof(buf));
            // if there was an error reading from the socket
            if (status == OTHER_ERROR)
            {
                error = true;
                break;
            }
            else if (status == SHUTDOWN)
            {
                b.shutdown();
            }

            if (status <= 0)
            {
                // if a has closed normally
                if (status == 0)
                    b.shutdown_outgoing();
                break;            
            }

            status = b.write(buf,status);
            // if there was an error writing to the socket then break
            if (status == OTHER_ERROR)
            {
                error = true;
                break;
            }
            
            if (status <= 0)
                break;            
        }


        // if there was an error then shutdown both connections
        if (error)
        {
            a.shutdown();
            b.shutdown();
        }




        // wait for the other thread to end
        service_connection_running_mutex.lock();
        while(service_connection_running)
        {
            service_connection_running_signaler.wait();
        }
        service_connection_running_mutex.unlock();


        // make sure connections are shutdown
        a.shutdown();
        b.shutdown();


        // both threads have ended so the connections are no longer needed
        cons_mutex.lock();
        A = 0;
        B = 0;
        cons_mutex.unlock();


        // if service_connection terminated due to an error then set error to true
        service_connection_error_mutex.lock();
        if (service_connection_error)
            error = true;
        service_connection_error_mutex.unlock();


        // if we are ending because of an error
        if (error)
        {

            // signal that the link() function is ending
            running_mutex.lock();
            running = false;
            running_signaler.broadcast();
            running_mutex.unlock();

            // throw the exception for this error
            throw dlib::socket_error (
                ECONNECTION,
                "a connection returned an error in linker::link()"
                );
         
        }

        // signal that the link() function is ending
        running_mutex.lock();
        running = false;
        running_signaler.broadcast();
        running_mutex.unlock();
    }

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

    void linker::
    service_connection (
        void* param
    )
    {
        linker& p = *static_cast<linker*>(param);

        p.cons_mutex.lock();
        // if the connections are gone for whatever reason then return
        if (p.A == 0 || p.B == 0)
        {
            // signal that this function is ending
            p.service_connection_running_mutex.lock();
            p.service_connection_running = false;
            p.service_connection_running_signaler.broadcast();
            p.service_connection_running_mutex.unlock();
            return;
        }
        connection& a = *p.A;
        connection& b = *p.B;
        p.cons_mutex.unlock();



        // forward data from b to a
        char buf[200];
        int status;
        bool error = false;
        while (true)
        {
            status = b.read(buf,sizeof(buf));
            // if there was an error reading from the socket
            if (status == OTHER_ERROR)
            {
                error = true;
                break;
            }
            else if (status == SHUTDOWN)
            {
                a.shutdown();
            }


            if (status <= 0)
            {
                // if b has closed normally 
                if (status == 0)
                    a.shutdown_outgoing();
                break;            
            }


            status = a.write(buf,status);
            // if there was an error writing to the socket then break
            if (status == OTHER_ERROR)
            {
                error = true;
                break;
            }
            
            if (status <= 0)
                break;            
        }


        // if there was an error then shutdown both connections
        if (error)
        {
            a.shutdown();
            b.shutdown();
        }


        // if there was an error then signal that
        if (error)
        {
            p.service_connection_error_mutex.lock();
            p.service_connection_error = true;
            p.service_connection_error_mutex.unlock();
        }

        // signal that this function is ending
        p.service_connection_running_mutex.lock();
        p.service_connection_running = false;
        p.service_connection_running_signaler.broadcast();
        p.service_connection_running_mutex.unlock();

    }

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

}
#endif // DLIB_LINKER_KERNEL_1_CPp_