/*
 * Copyright (C) 2005-2008 by Daniel Wagner
 *
 * This file is part of FFADO
 * FFADO = Free Firewire (pro-)audio drivers for linux
 *
 * FFADO is based upon FreeBoB
 *
 * 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) version 3 of the License.
 *
 * 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/>.
 *
 */

#include "bebob/bebob_functionblock.h"
#include "bebob/bebob_avdevice_subunit.h"
#include "bebob/bebob_avdevice.h"
#include "bebob/bebob_avplug.h"
#include "libieee1394/configrom.h"

#include "libavc/general/avc_plug_info.h"
#include "libavc/streamformat/avc_extended_stream_format.h"
#include "libutil/cmd_serialize.h"

#include <sstream>

using namespace AVC;

//////////////////////////

BeBoB::SubunitAudio::SubunitAudio( AVC::Unit& avDevice,
                                   subunit_t id )
    : AVC::SubunitAudio( avDevice, id )
{
}

BeBoB::SubunitAudio::SubunitAudio()
    : AVC::SubunitAudio()
{
}

BeBoB::SubunitAudio::~SubunitAudio()
{
    for ( FunctionBlockVector::iterator it = m_functions.begin();
          it != m_functions.end();
          ++it )
    {
        delete *it;
    }
}

AVC::Plug *
BeBoB::SubunitAudio::createPlug( AVC::Unit* unit,
                     AVC::Subunit* subunit,
                     AVC::function_block_type_t functionBlockType,
                     AVC::function_block_type_t functionBlockId,
                     AVC::Plug::EPlugAddressType plugAddressType,
                     AVC::Plug::EPlugDirection plugDirection,
                     AVC::plug_id_t plugId )
{

    return new BeBoB::Plug( unit,
                     subunit,
                     functionBlockType,
                     functionBlockId,
                     plugAddressType,
                     plugDirection,
                     plugId );
}

bool
BeBoB::SubunitAudio::discover()
{
    debugOutput(DEBUG_LEVEL_NORMAL, "Discovering %s...\n", getName());

    // discover the AV/C generic part
    if ( !AVC::SubunitAudio::discover() ) {
        return false;
    }

    // do the remaining BeBoB audio subunit discovery
    if ( !discoverFunctionBlocks() ) {
        debugError( "function block discovering failed\n" );
        return false;
    }

    return true;
}

bool
BeBoB::SubunitAudio::discoverConnections()
{
    debugOutput(DEBUG_LEVEL_NORMAL, "Discovering connections...\n");
    if ( !Subunit::discoverConnections() ) {
        return false;
    }

    for ( FunctionBlockVector::iterator it = m_functions.begin();
          it != m_functions.end();
          ++it )
    {
        FunctionBlock* function = *it;
        if ( !function->discoverConnections() ) {
            debugError( "functionblock connection discovering failed ('%s')\n",
                        function->getName() );
            return false;
        }
    }

    return true;
}

const char*
BeBoB::SubunitAudio::getName()
{
    return "BeBoB::AudioSubunit";
}

bool
BeBoB::SubunitAudio::discoverFunctionBlocks()
{
    debugOutput( DEBUG_LEVEL_NORMAL,
                 "Discovering function blocks...\n");

    if ( !discoverFunctionBlocksDo(
             ExtendedSubunitInfoCmd::eFBT_AudioSubunitSelector) )
    {
        debugError( "Could not discover function block selector\n" );
        return false;
    }
    if ( !discoverFunctionBlocksDo(
             ExtendedSubunitInfoCmd::eFBT_AudioSubunitFeature) )
    {
        debugError( "Could not discover function block feature\n" );
        return false;
    }
    if ( !discoverFunctionBlocksDo(
             ExtendedSubunitInfoCmd::eFBT_AudioSubunitProcessing) )
    {
        debugError( "Could not discover function block processing\n" );
        return false;
    }
    if ( !discoverFunctionBlocksDo(
             ExtendedSubunitInfoCmd::eFBT_AudioSubunitCodec) )
    {
        debugError( "Could not discover function block codec\n" );
        return false;
    }

    // print a function block list
#ifdef DEBUG
    if ((int)getDebugLevel() >= DEBUG_LEVEL_NORMAL) {

        for ( FunctionBlockVector::iterator it = m_functions.begin();
            it != m_functions.end();
            ++it )
        {
            debugOutput(DEBUG_LEVEL_NORMAL, "%20s FB, type 0x%X, id=%d\n",
                (*it)->getName(),
                (*it)->getType(),
                (*it)->getId());
        }
    }
#endif

    return true;
}

bool
BeBoB::SubunitAudio::discoverFunctionBlocksDo(
    ExtendedSubunitInfoCmd::EFunctionBlockType fbType )
{
    int page = 0;
    bool cmdSuccess = false;
    bool finished = false;

    do {
        ExtendedSubunitInfoCmd
        extSubunitInfoCmd( m_unit->get1394Service() );
        extSubunitInfoCmd.setNodeId( m_unit->getConfigRom().getNodeId() );
        extSubunitInfoCmd.setCommandType( AVCCommand::eCT_Status );
        extSubunitInfoCmd.setSubunitId( getSubunitId() );
        extSubunitInfoCmd.setSubunitType( getSubunitType() );
        extSubunitInfoCmd.setVerbose( (int)getDebugLevel() );

        extSubunitInfoCmd.m_fbType = fbType;
        extSubunitInfoCmd.m_page = page;

        cmdSuccess = extSubunitInfoCmd.fire();
        if ( cmdSuccess
             && ( extSubunitInfoCmd.getResponse()
                  == AVCCommand::eR_Implemented ) )
        {
            for ( ExtendedSubunitInfoPageDataVector::iterator it =
                      extSubunitInfoCmd.m_infoPageDatas.begin();
                  cmdSuccess
                      && ( it != extSubunitInfoCmd.m_infoPageDatas.end() );
                  ++it )
            {
                cmdSuccess = createFunctionBlock( fbType, **it );
            }
            if ( ( extSubunitInfoCmd.m_infoPageDatas.size() != 0 )
                 && ( extSubunitInfoCmd.m_infoPageDatas.size() == 5 ) )
            {
                page++;
            } else {
                finished = true;
            }
        } else {
            finished = true;
        }
    } while ( cmdSuccess && !finished );

    return cmdSuccess;
}

bool
BeBoB::SubunitAudio::createFunctionBlock(
    ExtendedSubunitInfoCmd::EFunctionBlockType fbType,
    ExtendedSubunitInfoPageData& data )
{
    FunctionBlock::ESpecialPurpose purpose
        = convertSpecialPurpose(  data.m_functionBlockSpecialPupose );

    FunctionBlock* fb = 0;

    switch ( fbType ) {
    case ExtendedSubunitInfoCmd::eFBT_AudioSubunitSelector:
    {
        fb = new FunctionBlockSelector( *this,
                                        data.m_functionBlockId,
                                        purpose,
                                        data.m_noOfInputPlugs,
                                        data.m_noOfOutputPlugs,
                                        (int)getDebugLevel() );
    }
    break;
    case ExtendedSubunitInfoCmd::eFBT_AudioSubunitFeature:
    {
        fb = new FunctionBlockFeature( *this,
                                       data.m_functionBlockId,
                                       purpose,
                                       data.m_noOfInputPlugs,
                                       data.m_noOfOutputPlugs,
                                       (int)getDebugLevel() );
    }
    break;
    case ExtendedSubunitInfoCmd::eFBT_AudioSubunitProcessing:
    {
        switch ( data.m_functionBlockType ) {
        case ExtendedSubunitInfoCmd::ePT_EnhancedMixer:
        {
            fb = new FunctionBlockEnhancedMixer( *this,
                                                 data.m_functionBlockId,
                                                 purpose,
                                                 data.m_noOfInputPlugs,
                                                 data.m_noOfOutputPlugs,
                                                 (int)getDebugLevel() );
        }
        break;
        case ExtendedSubunitInfoCmd::ePT_Mixer:
        case ExtendedSubunitInfoCmd::ePT_Generic:
        case ExtendedSubunitInfoCmd::ePT_UpDown:
        case ExtendedSubunitInfoCmd::ePT_DolbyProLogic:
        case ExtendedSubunitInfoCmd::ePT_3DStereoExtender:
        case ExtendedSubunitInfoCmd::ePT_Reverberation:
        case ExtendedSubunitInfoCmd::ePT_Chorus:
        case ExtendedSubunitInfoCmd::ePT_DynamicRangeCompression:
        default:
            /* It is no use to add a dummy FunctionBlockProcessing because
               then the function type is not set in FunctionBlockProcessing. 
               When we try to discover the plugs attached to this function block
               it will fail. It's better just to skip them. */
            debugOutput( DEBUG_LEVEL_NORMAL, "Found a processing subfunction (type %d) which is not supported. "
                         "It will be ignored.\n",
                         data.m_functionBlockType);
            return true;
        }
    }
    break;
    case ExtendedSubunitInfoCmd::eFBT_AudioSubunitCodec:
    {
            /* It is no use to add a dummy FunctionBlockProcessing because
               then the function type is not set in FunctionBlockProcessing. 
               When we try to discover the plugs attached to this function block
               it will fail. It's better just to skip them. */
            debugOutput( DEBUG_LEVEL_NORMAL, "Found a codec subfunction (type %d) which is not supported. "
                         "It will be ignored.\n",
                         data.m_functionBlockType);
            return true;
    }
    break;
    default:
        debugError( "Unhandled function block type found\n" );
        return false;
    }

    if ( !fb ) {
        debugError( "Could create function block\n" );
        return false;
    }
    if ( !fb->discover() ) {
        debugError( "Could not discover function block %s\n",
                    fb->getName() );
        delete fb;
        return false;
    }
    m_functions.push_back( fb );

    return true;
}

BeBoB::FunctionBlock::ESpecialPurpose
BeBoB::SubunitAudio::convertSpecialPurpose(
    function_block_special_purpose_t specialPurpose )
{
    FunctionBlock::ESpecialPurpose p;
    switch ( specialPurpose ) {
    case ExtendedSubunitInfoPageData::eSP_InputGain:
        p  = FunctionBlock::eSP_InputGain;
        break;
    case ExtendedSubunitInfoPageData::eSP_OutputVolume:
        p = FunctionBlock::eSP_OutputVolume;
    break;
    default:
        p = FunctionBlock::eSP_NoSpecialPurpose;
    }
    return p;
}

bool
BeBoB::SubunitAudio::serializeChild( std::string basePath,
                                     Util::IOSerialize& ser ) const
{
    bool result = true;
    int i = 0;

    for ( FunctionBlockVector::const_iterator it = m_functions.begin();
          it != m_functions.end();
          ++it )
    {
        FunctionBlock* pFB = *it;
        std::ostringstream strstrm;
        strstrm << basePath << "FunctionBlock" << i << "/";

        result &= pFB->serialize( strstrm.str() , ser );

        i++;
    }

    return result;
}

bool
BeBoB::SubunitAudio::deserializeChild( std::string basePath,
                                       Util::IODeserialize& deser,
                                       AVC::Unit& avDevice )
{
    int i = 0;
    bool bFinished = false;
    do {
        std::ostringstream strstrm;
        strstrm << basePath << "FunctionBlock" << i << "/";
        FunctionBlock* pFB = FunctionBlock::deserialize( strstrm.str(),
                                                         deser,
                                                         avDevice,
                                                         *this );
        if ( pFB ) {
            m_functions.push_back( pFB );
            i++;
        } else {
            bFinished = true;
        }
    } while ( !bFinished );

    return true;
}

bool
BeBoB::SubunitAudio::deserializeUpdateChild( std::string basePath,
                                             Util::IODeserialize& deser )
{
    bool result = true;
    int i = 0;

    for ( FunctionBlockVector::iterator it = m_functions.begin();
          it != m_functions.end();
          ++it )
    {
        std::ostringstream strstrm;
        strstrm << basePath << "FunctionBlock" << i << "/";

        result &= (*it)->deserializeUpdate( basePath, deser );
        
        i++;
    }

    return result;
}

////////////////////////////////////////////

BeBoB::SubunitMusic::SubunitMusic( AVC::Unit& avDevice,
                                   subunit_t id )
    : AVC::SubunitMusic( avDevice, id )
{
}

BeBoB::SubunitMusic::SubunitMusic()
    : AVC::SubunitMusic()
{
}

BeBoB::SubunitMusic::~SubunitMusic()
{
}

AVC::Plug *
BeBoB::SubunitMusic::createPlug( AVC::Unit* unit,
                     AVC::Subunit* subunit,
                     AVC::function_block_type_t functionBlockType,
                     AVC::function_block_type_t functionBlockId,
                     AVC::Plug::EPlugAddressType plugAddressType,
                     AVC::Plug::EPlugDirection plugDirection,
                     AVC::plug_id_t plugId )
{

    return new BeBoB::Plug( unit,
                     subunit,
                     functionBlockType,
                     functionBlockId,
                     plugAddressType,
                     plugDirection,
                     plugId );
}

bool
BeBoB::SubunitMusic::discover()
{
    debugOutput(DEBUG_LEVEL_NORMAL, "Discovering %s...\n", getName());

    // discover the AV/C generic part
    if ( !AVC::SubunitMusic::discover() ) {
        return false;
    }

    // do the remaining BeBoB music subunit discovery
    // which is nothing

    return true;
}

const char*
BeBoB::SubunitMusic::getName()
{
    return "BeBoB::MusicSubunit";
}

bool
BeBoB::SubunitMusic::serializeChild( std::string basePath,
                                             Util::IOSerialize& ser ) const
{
    return true;
}

bool
BeBoB::SubunitMusic::deserializeChild( std::string basePath,
                                               Util::IODeserialize& deser,
                                               AVC::Unit& avDevice )
{
    return true;
}

bool 
BeBoB::SubunitMusic::deserializeUpdateChild( std::string basePath,
                                             Util::IODeserialize& deser )
{
    return true;
}
