/*
 * 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 3 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 <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include "DriverConfiguration.h"
#include "Console.h"

static bool WriteCharactersToFile( const char *pFileName, const char *pString )
{
    bool success = false;
    FILE *fh = fopen( pFileName, "a" );
    if( NULL != fh )
    {
        int result = fputs( pString, fh );
        if( result >= 0 )
            fputc( '\n', fh );
        if( result >= 0 )
            success = true;
        fclose( fh );
    }
    if( success )
        ConsolePrintf( PRIO_MEDIUM, "*** configured device: '%s = %s', success:%d\n", pFileName, pString, success );
    else
        ConsolePrintf( PRIO_ERROR, RED"DriverConfiguration.WriteCharactersToFile failed for file '%s', errno:'%s'"RESETCOLOR"\n",
        pFileName, GetErrnoString() );
    return success;
}

static bool WriteIntegerToFile( const char *pFileName, int intValue )
{
    char tempBuffer[16];
    snprintf( tempBuffer, sizeof( tempBuffer ), "%d", intValue );
    return WriteCharactersToFile( pFileName, tempBuffer );
}

static bool ReadFromFile( const char *pFileName, char *pString, uint16_t bufferLen )
{
    bool success = false;
    if( NULL == pString || 0 == bufferLen )
        return success;
    FILE *fh = fopen( pFileName, "r" );
    if( NULL != fh )
    {
        success = ( NULL != fgets( pString, bufferLen, fh ) );
        fclose( fh );
    }
    if( !success )
        ConsolePrintf( PRIO_ERROR, RED"DriverConfiguration.ReadFromFile failed for file '%s', errno:'%s'"RESETCOLOR"\n",
        pFileName, GetErrnoString() );
    return success;
}

static bool ExistsDevice( const char *pDeviceName )
{
    struct stat buffer;
    return ( stat( pDeviceName, &buffer ) == 0 );
}

static bool WaitForDevice( const char *pDeviceName )
{
    int timeout;
    bool deviceExists = false;
    for( timeout = 0; timeout < 40; timeout++ )
    {
        deviceExists = ExistsDevice( pDeviceName );
        if( deviceExists )
        {
            break;
        }
        else
        {
            usleep( 2500 );
        }
    }
    if( !deviceExists )
        ConsolePrintf( PRIO_ERROR, RED"Waiting for device '%s' to appear, timed out"RESETCOLOR"\n",
        pDeviceName );
    return deviceExists;
}

static bool GetDeviceDescription( uint8_t deviceInstance, char *deviceString, uint32_t stringLen )
{
    bool descriptionFound = false;
    char descriptionPath[64];
    char descriptionValue[32];

    if( NULL == deviceString )
        return descriptionFound;

    snprintf( descriptionPath, sizeof( descriptionPath ),
        "/sys/devices/virtual/most/mostcore/devices/mdev%d/description", deviceInstance );

    if( !ExistsDevice( descriptionPath ) )
        return descriptionFound;

    descriptionFound = ReadFromFile( descriptionPath, descriptionValue, sizeof( descriptionValue ) );
    if( descriptionFound )
    {
        strncpy( deviceString, descriptionValue, stringLen );
    }
    return descriptionFound;
}

static bool ExistsDeviceWithType( uint8_t deviceInstance, const char *deviceString, uint32_t stringLen )
{
    bool deviceFound = false;
    char interfacePath[64];
    char interfaceValue[32];

    if( NULL == deviceString )
        return deviceFound;

    snprintf( interfacePath, sizeof( interfacePath ), "/sys/devices/virtual/most/mostcore/devices/mdev%d/interface",
        deviceInstance );

    if( !ExistsDevice( interfacePath ) )
        return deviceFound;

    deviceFound = ReadFromFile( interfacePath, interfaceValue, sizeof( interfaceValue ) );
    if( deviceFound )
    {
        deviceFound = ( 0 == strncmp( interfaceValue, deviceString, stringLen ) );
    }
    return deviceFound;
}

static bool GetAlsaConfiguration( uint16_t subBufferSize, uint8_t *amountOfChannels, uint8_t *bitDepth )
{
    if( NULL == amountOfChannels || NULL == bitDepth )
    {
        ConsolePrintf( PRIO_ERROR, RED"GetAlsaConfiguration was called with invalid parameters"RESETCOLOR"\n" );
        return false;
    }
    switch( subBufferSize )
    {
    case 2:
        *amountOfChannels = 1;
        *bitDepth = 16;
        break;
    case 4:
        *amountOfChannels = 2;
        *bitDepth = 16;
        break;
    case 6:
        *amountOfChannels = 2;
        *bitDepth = 24;
        break;
    case 8:
        *amountOfChannels = 2;
        *bitDepth = 32;
        break;
    case 12:
        *amountOfChannels = 6;
        *bitDepth = 16;
        break;
    case 18:
        *amountOfChannels = 6;
        *bitDepth = 24;
        break;
    case 16:
        *amountOfChannels = 8;
        *bitDepth = 16;
        break;
    case 24:
        *amountOfChannels = 8;
        *bitDepth = 24;
        break;
    default:
        ConsolePrintf( PRIO_ERROR, RED"Configure ALSA device was called"\
            " with unknown sub-buffer size: %d"RESETCOLOR"\n", subBufferSize );
        return false;
    }
    return true;
}

bool ExistsUsbDeviceInstance( uint8_t deviceInstance )
{
    uint32_t i;
    bool found = false;
    uint8_t curDevInst = 0;
    char lastDescr[64];
    lastDescr[0] = '\0';
    for( i = 0; i < 12; i++ )
    {
        uint32_t curDescrLen;
        char curDescr[64];
        if( !ExistsDeviceWithType( i, "usb", 3 ) )
            continue;

        if( !GetDeviceDescription( i, curDescr, sizeof( curDescr ) ) )
            continue;

        curDescrLen = strnlen( curDescr, sizeof( curDescr ) );
        if( curDescrLen <= 2 )
            continue;

        //Cut away the last two characters, as they are different for the 3 interfaces of INIC
        curDescrLen -= 2;

        if( 0 == strncmp( curDescr, lastDescr, curDescrLen ) )
            continue;

        strncpy( lastDescr, curDescr, curDescrLen );
        lastDescr[curDescrLen] = '\0';

        if( curDevInst++ != deviceInstance )
            continue;

        found = true;
        break;
    }
    return found;
}

bool ExistsMlbDeviceInstance( uint8_t deviceInstance )
{
    return ExistsDeviceWithType( deviceInstance, "mlb", 3 );
}

bool ExistsI2CDeviceInstance( uint8_t deviceInstance )
{
    return ExistsDeviceWithType( deviceInstance, "i2c", 3 );
}

bool GetUsbDeviceNames( uint8_t deviceInstance, uint8_t endpointAddress, char *deviceName, uint16_t deviceNameLength,
    char *systemName, uint16_t systemNameLength, char *linkName, uint16_t linkNameLength )
{
    uint32_t i;
    bool systemFound = false;
    uint8_t curDevInst = 0;
    char lastDescr[64];
    lastDescr[0] = '\0';
    char endpointBuffer[16];
    char systemSourceDir[64];
    DIR *d;
    struct dirent *dir;

    if( NULL == deviceName || NULL == systemName || NULL == linkName )
        return false;

    deviceName[0] = '\0';
    systemName[0] = '\0';

    for( i = 0; !systemFound && i < 12; i++ )
    {
        uint32_t curDescrLen;
        char curDescr[64];
        if( !ExistsDeviceWithType( i, "usb", 3 ) )
            continue;

        if( !GetDeviceDescription( i, curDescr, sizeof( curDescr ) ) )
            continue;

        curDescrLen = strnlen( curDescr, sizeof( curDescr ) );
        if( curDescrLen <= 2 )
            continue;

        //Cut away the last two characters, as they are different for the 3 interfaces of INIC
        curDescrLen -= 2;

        if( ( '\0' != lastDescr[0] )
            && ( 0 != strncmp( curDescr, lastDescr, curDescrLen ) ) )
        {
            ++curDevInst;
        }

        strncpy( lastDescr, curDescr, curDescrLen );
        lastDescr[curDescrLen] = '\0';

        if( curDevInst < deviceInstance )
            continue;
        else if( curDevInst > deviceInstance )
            break;

        snprintf( endpointBuffer, sizeof( endpointBuffer ), "ep%02x", endpointAddress );
        snprintf( systemSourceDir, sizeof( systemSourceDir ), "/sys/devices/virtual/most/mostcore/devices/mdev%d", i );
        d = opendir( systemSourceDir );
        if( d )
        {
            while( ( dir = readdir( d ) ) != NULL )
            {
                if( strstr( dir->d_name, endpointBuffer ) )
                {
                    snprintf( systemName, systemNameLength, "%s/%s", systemSourceDir, dir->d_name );
                    snprintf( deviceName, deviceNameLength, "/dev/mdev%d-%s", deviceInstance, dir->d_name );
                    snprintf( linkName, linkNameLength, "mdev%d:%s:mdev%d-%s", i, dir->d_name, deviceInstance,
                        dir->d_name );
                    systemFound = true;
                    break;
                }
            }
            closedir( d );
        }
    }
    return systemFound;
}

bool GetMlbDeviceNames( uint8_t deviceInstance, uint8_t mlbChannelAddress, char *deviceName, uint16_t deviceNameLength,
    char *systemName, uint16_t systemNameLength, char *linkName, uint16_t linkNameLength )
{
    bool systemFound = false;
    char channelBuffer[16];
    char systemSourceDir[64];
    DIR *d;
    struct dirent *dir;

    if( NULL == deviceName || NULL == systemName || NULL == linkName )
        return false;

    deviceName[0] = '\0';
    systemName[0] = '\0';

    snprintf( channelBuffer, sizeof( channelBuffer ), "ca%d", mlbChannelAddress );
    snprintf( systemSourceDir, sizeof( systemSourceDir ), "/sys/devices/virtual/most/mostcore/devices/mdev%d",
        deviceInstance );
    d = opendir( systemSourceDir );
    if( d )
    {
        while( ( dir = readdir( d ) ) != NULL )
        {
            if( strstr( dir->d_name, channelBuffer ) )
            {
                snprintf( systemName, systemNameLength, "%s/%s", systemSourceDir, dir->d_name );
                snprintf( deviceName, deviceNameLength, "/dev/mdev%d-%s", deviceInstance, dir->d_name );
                snprintf( linkName, linkNameLength, "mdev%d:%s:mdev%d-%s", deviceInstance, dir->d_name, deviceInstance,
                    dir->d_name );
                systemFound = true;
                break;
            }
        }
        closedir( d );
    }
    return systemFound;
}

bool GetI2CDeviceNames( uint8_t deviceInstance, bool isTx, char *deviceName, uint16_t deviceNameLength,
    char *systemName, uint16_t systemNameLength, char *linkName, uint16_t linkNameLength )
{
    bool systemFound = false;
    char systemSourceDir[64];
    DIR *d;
    struct dirent *dir;

    if( NULL == deviceName || NULL == systemName || NULL == linkName )
        return false;

    deviceName[0] = '\0';
    systemName[0] = '\0';

    snprintf( systemSourceDir, sizeof( systemSourceDir ), "/sys/devices/virtual/most/mostcore/devices/mdev%d",
        deviceInstance );
    d = opendir( systemSourceDir );
    if( d )
    {
        while( ( dir = readdir( d ) ) != NULL )
        {
            if( strstr( dir->d_name, ( isTx ? "tx" : "rx" ) ) )
            {
                snprintf( systemName, systemNameLength, "%s/%s", systemSourceDir, dir->d_name );
                snprintf( deviceName, deviceNameLength, "/dev/mdev%d-%s", deviceInstance, dir->d_name );
                snprintf( linkName, linkNameLength, "mdev%d:%s:mdev%d-%s", deviceInstance, dir->d_name, deviceInstance,
                    dir->d_name );
                systemFound = true;
                break;
            }
        }
        closedir( d );
    }
    return systemFound;
}

bool CloseMostChannel( const char *device )
{
    return true;
}

bool ConfigureMostChannel( const char *device, EPDataType_t mostType, EPDirection_t direction, uint32_t numBuf,
    uint32_t bufSize )
{
    static const char *controlType = "control";
    static const char *asyncType = "async";
    static const char *syncType = "sync";
    static const char *isocType = "isoc_avp";
    static const char *rxDirection = "dir_rx";
    static const char *txDirection = "dir_tx";
    bool success = true;
    char tempBuffer[128];
    const char *typeString = NULL;
    const char *directionString = NULL;

    switch( mostType )
    {
    case EP_Control:
        typeString = controlType;
        break;
    case EP_Asynchron:
        typeString = asyncType;
        break;
    case EP_Synchron:
        typeString = syncType;
        break;
    case EP_Isochron:
        typeString = isocType;
        break;
    default:
        return false;
    }

    switch( direction )
    {
    case EPDIR_IN:
        directionString = txDirection;
        break;
    case EPDIR_OUT:
        directionString = rxDirection;
        break;
    default:
        return false;
    }

    snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_datatype", device );				/// Setting data type
    success = WriteCharactersToFile( tempBuffer, typeString );
    if( success )
    {
        snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_direction", device );			/// Setting direction
        success = WriteCharactersToFile( tempBuffer, directionString );
    }
    if( success )
    {
        snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_number_of_buffers", device );	/// Setting amount of buffers
        success = WriteIntegerToFile( tempBuffer, numBuf );
    }
    if( success )
    {
        snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_buffer_size", device );			/// Setting amount of buffers
        success = WriteIntegerToFile( tempBuffer, bufSize );
    }
    return success;
}

bool ConfigureIsocChannel( const char *device, uint32_t subbufferSize, uint32_t packetsPerTransaction )
{
    char tempBuffer[128];
    bool success;
    snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_subbuffer_size", device ); 	/// Setting the subbuffer size in bytes
    success = WriteIntegerToFile( tempBuffer, subbufferSize );

    snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_packets_per_xact", device ); 	/// Setting the packets per transaction
    success = WriteIntegerToFile( tempBuffer, ( packetsPerTransaction & 0xFF ) );
    return success;
}

bool ConfigureSyncChannel( const char *device, uint32_t syncBlockWidth, uint32_t amounOfSyncFrames )
{
    char tempBuffer[128];
    bool success;
    snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_packets_per_xact", device ); 	/// Setting the amount of frames in one USB frame
    success = WriteIntegerToFile( tempBuffer, amounOfSyncFrames );

    snprintf( tempBuffer, sizeof( tempBuffer ), "%s/set_subbuffer_size", device ); 	/// Setting the blockwidth of a single frame
    success = WriteIntegerToFile( tempBuffer, syncBlockWidth );
    return success;
}

bool LinkToCharacterDevice( const char *linkName, const char *deviceName )
{
    bool success =
        WriteCharactersToFile( "/sys/devices/virtual/most/mostcore/aims/cdev/add_link", linkName );
    if( success )
    {
        success = WaitForDevice( deviceName );
    }
    if( success )
    {
        chmod( deviceName, ( S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH )  );
    }
    return success;
}

bool LinkToAudioDevice( const char *linkName, uint16_t subBufferSize )
{
    char tempBuffer[128];
    uint8_t amountOfChannels;
    uint8_t bitDepth;
    if( !GetAlsaConfiguration( subBufferSize, &amountOfChannels, &bitDepth ) )
        return false;

    snprintf( tempBuffer, sizeof( tempBuffer ), "%s.%dx%d", linkName, amountOfChannels, bitDepth ); 	/// Add the channel information behind the Link Name

    //Be compatible to all versions of MOST Linux Driver. The name was changed of Sound AIM.
    bool success =
        WriteCharactersToFile( "/sys/devices/virtual/most/mostcore/aims/sound/add_link", tempBuffer );
    if( !success )
        success =
            WriteCharactersToFile( "/sys/devices/virtual/most/mostcore/aims/audio/add_link", tempBuffer );
    return success;
}

bool LinkToVideoForLinuxDevice( const char *linkName )
{
    bool success =
        WriteCharactersToFile( "/sys/devices/virtual/most/mostcore/aims/v4l/add_link", linkName );
    return success;
}

const char *GetErrnoString()
{
    switch( errno )
    {
    case 0:
        return "Nothing stored in errno";
    case 1:
        return "Operation not permitted";
    case 2:
        return "No such file or directory";
    case 3:
        return "No such process";
    case 4:
        return "Interrupted system call";
    case 5:
        return "I/O error";
    case 6:
        return "No such device or address";
    case 7:
        return "Argument list too long";
    case 8:
        return "Exec format error";
    case 9:
        return "Bad file number";
    case 10:
        return "No child processes";
    case 11:
        return "Try again";
    case 12:
        return "Out of memory";
    case 13:
        return "Permission denied";
    case 14:
        return "Bad address";
    case 15:
        return "Block device required";
    case 16:
        return "Device or resource busy";
    case 17:
        return "File exists";
    case 18:
        return "Cross-device link";
    case 19:
        return "No such device";
    case 20:
        return "Not a directory";
    case 21:
        return "Is a directory";
    case 22:
        return "Invalid argument";
    case 23:
        return "File table overflow";
    case 24:
        return "Too many open files";
    case 25:
        return "Not a typewriter";
    case 26:
        return "Text file busy";
    case 27:
        return "File too large";
    case 28:
        return "No space left on device";
    case 29:
        return "Illegal seek";
    case 30:
        return "Read-only file system";
    case 31:
        return "Too many links";
    case 32:
        return "Broken pipe";
    case 33:
        return "Math argument out of domain of func";
    case 34:
        return "Math result not representable";
    default:
        break;
    }
    return "Unknown";
}