/*
 * Video On Demand Samples
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "Console.h"

///Our shared memory key
static int shmkey = 072162537;
//The minimum priority
static ConsolePrio_t minPrio = PRIO_LOW;

/*! \cond PRIVATE */
typedef struct
{
    ///The first process will setup the critical section and set this variable to true.
    bool initialized;
    ///If set to true, multiple processeses are synced via shared memory.
    bool processSynced;
    ///If set to true, there is an segmented print ongoing (Start, Continue, Exit).
    bool criticalSection;
    //If is in a critical segmented print, this variable will hold the prio for Start, Continue, Exit.
    ConsolePrio_t criticalSectionPrio;
    ///Handle of the shared mutex.
    pthread_mutex_t mutex;
} sharedData_t;
/*! \endcond */

/*----------------------------------------------------------*/
/*! \brief Pointer to the shared memory instance.
 */
/*----------------------------------------------------------*/
static sharedData_t *data = NULL;

void ConsoleInit( bool synchronizeProcesses )
{
    pthread_mutexattr_t attr;

    if( synchronizeProcesses )
    {
        int shmid = shmget( shmkey, sizeof( sharedData_t ), IPC_CREAT | 0666 );
        if( ( sharedData_t * )-1 == ( data = ( sharedData_t* )shmat( shmid, NULL, 0 ) ) )
        {
            data = NULL;
            fprintf( stderr, RED"ConsoleInit failed, because shared memory could not be accessed."RESETCOLOR"\n" );
            return;
        }
    }
    else
    {
        data = ( sharedData_t * )calloc( 1, sizeof( sharedData_t ) );
    }
    if( ( NULL != data ) && !data->initialized )
    {
        data->processSynced = synchronizeProcesses;
        data->initialized = true;
        data->criticalSection = false;

        pthread_mutexattr_init( &attr );
        if( synchronizeProcesses )
            pthread_mutexattr_setpshared( &attr, PTHREAD_PROCESS_SHARED );
        else
            pthread_mutexattr_setpshared( &attr, PTHREAD_PROCESS_PRIVATE );
        pthread_mutex_init( &data->mutex, &attr );
    }
}

void ConsoleDeinit( void )
{
    if( NULL != data && !data->processSynced )
    {
        free( data );
        data = NULL;
    }
}

void ConsoleSetPrio( ConsolePrio_t prio )
{
    minPrio = prio;
}

void ConsolePrintf( ConsolePrio_t prio, const char *statement, ... )
{
    int err;
    if( prio < minPrio || NULL == statement )
        return;
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintf data was null"RESETCOLOR"\n" );
        return;
    }
    if( 0 != ( err = pthread_mutex_lock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintf, pthread_mutex_lock error: %d"RESETCOLOR"\n", err );
        return;
    }

    va_list args;
    va_start( args, statement );
    vfprintf( stderr, statement, args );
    va_end( args );

    if( 0 != ( err = pthread_mutex_unlock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintf, pthread_mutex_unlock error: %d"RESETCOLOR"\n", err );
        return;
    }
}

void ConsolePrintfStart( ConsolePrio_t prio, const char *statement, ... )
{
    int err;
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintfStart data was null"RESETCOLOR"\n" );
        return;
    }
    if( 0 != ( err = pthread_mutex_lock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintfStart, pthread_mutex_lock error: %d"RESETCOLOR"\n", err );
        return;
    }
    data->criticalSection = true;
    data->criticalSectionPrio = prio;

    if( data->criticalSectionPrio >= minPrio && NULL != statement )
    {
        va_list args;
        va_start( args, statement );
        vfprintf( stderr, statement, args );
        va_end( args );
    }
}

void ConsolePrintfContinue( const char *statement, ... )
{
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintfContinue data was null"RESETCOLOR"\n" );
        return;
    }
    if( !data->criticalSection )
    {
        fprintf( stderr, RED"ConsolePrintfContinue not in critical section"RESETCOLOR"\n" );
        return;
    }

    if( data->criticalSectionPrio >= minPrio && NULL != statement )
    {
        va_list args;
        va_start( args, statement );
        vfprintf( stderr, statement, args );
        va_end( args );
    }
}

void ConsolePrintfExit( const char *statement, ... )
{
    int err;
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintfExit data was null"RESETCOLOR"\n" );
        return;
    }
    if( !data->criticalSection )
    {
        fprintf( stderr, RED"ConsolePrintfExit not in critical section"RESETCOLOR"\n" );
        return;
    }
    if( data->criticalSectionPrio >= minPrio && NULL != statement )
    {
        va_list args;
        va_start( args, statement );
        vfprintf( stderr, statement, args );
        va_end( args );
    }
    data->criticalSection = false;
    if( 0 != ( err = pthread_mutex_unlock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintfExit, pthread_mutex_unlock error: %d"RESETCOLOR"\n", err );
    }
}