// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// cadvertizehandler.cpp
// 
//
/**
@file
@internalTechnology
*/
//System include 
#include <mdns/ccacheentry.h>

//UserInclude
#include "cadvertizehandler.h"
__FLOG_STMT(_LIT8(KComponent,"MDNSServer");)

/*
 * Two phase constructor
 * @param aMessagHandler areference to messagehandler object.
 * @param aAutoResolveEnabled If true sercice name conflict will be handled.
 * aAutoResolveEnabled is True by default
 */
CAdvertizeHandler* CAdvertizeHandler::NewL(CMessageHandler& aMessageHandler,TBool aAutoResolveEnabled)
	{
	CAdvertizeHandler* self = new (ELeave) CAdvertizeHandler(aMessageHandler,aAutoResolveEnabled);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}
	
/*
 * Destructor
 */
CAdvertizeHandler::~CAdvertizeHandler()
	{
	__FLOG(_L8("CAdvertizeHandler::~CAdvertizeHandler- Entry"));
	iData.ResetAndDestroy();
	iData.Close();
	iName.Close();
	iProbeName.Close();
	iProbeType.Close();
	__FLOG(_L8("CAdvertizeHandler::~CAdvertizeHandler- Exit"));
	__FLOG_CLOSE;
	}
/*
 * No incoming packet for this so no need to handle
 */	
void CAdvertizeHandler::HandleIncomingPacketL(CDnsMessage& /*aMessage*/)
	{
	__FLOG(_L8("CAdvertizeHandler::HandleIncomingPacketL- Entry"));
	//Nothing To Do ............
	__FLOG(_L8("CAdvertizeHandler::HandleIncomingPacketL- Exit"));
	}
	
static void CleanUpCache(TAny* aAny)
    {
    
    //RPointerArray <CCacheEntry>  records = static_cast < RPointerArray <CCacheEntry> > (*aAny);
    RPointerArray <CCacheEntry>*  records = static_cast < RPointerArray <CCacheEntry> *> (aAny);
    records->ResetAndDestroy();
    records->Close();
    }
/*
 * Constructor
 * @param aMessageHandler reference to messagehandler
 * @param aAutoResolveEnabled will be true by default ,if true servicename conflict
 * will be handled by default
 */
CAdvertizeHandler::CAdvertizeHandler(CMessageHandler& aMessageHandler,TBool aAutoResolveEnabled):CBaseHandler(aMessageHandler)
	{
	__FLOG(_L8("CAdvertizeHandler::CAdvertizeHandler- Entry"));
	iAutoResolveEnabled = aAutoResolveEnabled;
	iCurrentProbeState = EIdle;
	iProbeCounter = 0 ;
	__FLOG(_L8("CAdvertizeHandler::CAdvertizeHandler- Exit"));
	}
	
/*
 * Twophase constructor
 */
void CAdvertizeHandler::ConstructL()
	{
	__FLOG_OPEN(KMDNSSubsystem, KComponent);
	__FLOG(_L8("CAdvertizeHandler::ConstructL- Entry"));
	CBaseHandler::ConstructL();
	//Nothing 
	__FLOG(_L8("CAdvertizeHandler::ConstructL- Exit"));
	}
/*
 * Keeps a copy of the records to be published and starts the active object.
 * @param aData an array of records to be published.
 * @param aSessionId session id initiating the advertizement.
 */
void CAdvertizeHandler::AdvertizePacketL(const RPointerArray<CDnsResourceData> aData, TInt aSessionId,TBool aIsUpdate)
    {
    __FLOG(_L8("CAdvertizeHandler::AdvertizePacketL- Entry"));
    iName.Close();
    iName.Create(aData[0]->Name());
    iProbeName.Close();
    iProbeName.CreateL(KMaxLength);
    iProbeName.Append(iName);
    iCurrentProbeState = EStart;
    iData.ResetAndDestroy();
    for(TInt i =0 ; i<aData.Count();i++)
        {
        iData.AppendL(aData[i]);
        }
     // assign the session id so that, we can send bye-bye packet when the session is closed
    iSessionId = aSessionId;
    if(aIsUpdate)
        {
        iCurrentProbeState = EIdle;
        TBool isExist = DefensiveResponseL();
        if(!isExist)
            {
            User::Leave(KErrNotFound);
            }
        SendAnnouncementL();
        InsertInCache();
        return;
        }
    Schedule();
    __FLOG(_L8("CAdvertizeHandler::AdvertizePacketL- Exit"));
    }
/*
 * This is a callback function will be notified whenever a packet sent from this
 * client is sent to the network.
 * @param aError any error in sending the packet.
 */
void CAdvertizeHandler::OnPacketSendL(TInt aError)
    {
    __FLOG(_L8("CAdvertizeHandler::OnPacketSendL- Entry"));
    if(aError != KErrNone)
        {
        MessageHandler().NotifyServicePublishL(iName,EFail,iSessionId);
        }
    
    if( iCurrentProbeState != EIdle)
            {
            After(KProbeDelay*1000);//Give more delay than this
            return;
            }
    //When a service is published successfully ,iCurrentProbe state will be 
    //EIdle ,this state change will be used to notify the client .
    MessageHandler().NotifyServicePublishL(iName,ESuccess,iSessionId);
    __FLOG(_L8("CAdvertizeHandler::OnPacketSendL- Exit"));
    }

void CAdvertizeHandler::RunL()
    {    
    __FLOG(_L8("CAdvertizeHandler::RunL- Entry"));
    TTime currentTime;

    if(iCurrentProbeState <= EProbeComplete)
        {
        if(DefensiveResponseL())
            {       
            iCurrentProbeState = EStart;                
            if(iAutoResolveEnabled)
                {
                GenerateNonConflictingName(); 
                if(iProbeCounter >= KMaxFailures)
                    {
                    //If Probe Failure rate is greater then 15 per second, wait for 
                    //5 seconds before probing again
                    iProbeCounter = 0;
                    After(5*1000*1000);             
                    }
                }       
              else //  to intimate server of failure
                  {
                  MessageHandler().NotifyServicePublishL(iName,EConflictWithoutAutoResolve,iSessionId);
                  }
            
            }       
        }

    switch(iCurrentProbeState)
        {       
        case EIdle:
        break;//Do Nothing
    
        case EStart:
            {
            currentTime.UniversalTime();
            TInt64 randseed = currentTime.Int64();                  
    
            //Random time between 0 & 250 ms
            TInt delay = Math::Rand(randseed) % 250;
            iCurrentProbeState = EProbeFirstUnicast;
            //Convert to microsecond
    
            After(delay*1000);          
            }               
            break;
    
        case EProbeFirstUnicast:
            {
            SendProbeL(ETrue);  
            iCurrentProbeState = EProbeSecondUnicast;                           
            }
            break;
    
        case EProbeSecondUnicast:
            {               
            SendProbeL(ETrue);  
            iCurrentProbeState = EProbeMulticast;                                       
            }
            break;      
    
        case EProbeMulticast:
            {
            SendProbeL(EFalse); 
            iCurrentProbeState = EProbeComplete;            
            }
            break;
    
        case EProbeComplete:
            {
            iCurrentProbeState = EFirstAnnouncement;
            //Probe succeeded ,insert in cache 
            InsertInCache();    
            After(KProbeDelay*1000);
            //Some Delay ?? Required or defensive response HAS TO come within 750ms?
            }
            break;
    
        case EFirstAnnouncement:
            {
            SendAnnouncementL();
            iCurrentProbeState = ESecondAnnouncement;
            //After(1000);// A Millisecond delay: Required??    
            }
            break;
    
        case ESecondAnnouncement:
            {
            SendAnnouncementL();
            iCurrentProbeState = EIdle;
            }
            break;  
        }
    __FLOG(_L8("CAdvertizeHandler::RunL- Exit"));
    }
/*
 * Handles any leave in RunL
 * @param aError error with which runL leaves.
 */
TInt CAdvertizeHandler::RunError(TInt aError)
    {
    __FLOG(_L8("CAdvertizeHandler::RunError- Entry"));
    return aError;
    }

/*
 * Self completes the request.
 */
void CAdvertizeHandler::Schedule()
    {
    __FLOG(_L8("CAdvertizeHandler::Schedule- Entry"));
    TRequestStatus* status(&iStatus);
    *status = KRequestPending;
    SetActive();
    User::RequestComplete(status, KErrNone);
    __FLOG(_L8("CAdvertizeHandler::Schedule- Exit"));
    }

/*
 * Function will be called when there is a conflict.
 * this will change the publishing name by appending a 
 * number at the end of it. 
 * @return iProbeCounter returns probecounter value.
 */
TInt CAdvertizeHandler::GenerateNonConflictingName()
    {
    __FLOG(_L8("CAdvertizeHandler::GenerateNonConflictingName- Entry"));
    _LIT8(KDot,".");

        RBuf8 oldName;
        oldName.CreateL(iName);
        
        ++iProbeCounter;
        TBuf8 <KMaxLength> newName;
        iName.Close();  
        iName.Create(KMaxLength);
        iName.Append(iProbeName);
        
        TInt pos = iName.Locate('.');
        _LIT8(KOpenBrace,"(");
        newName.Append(KOpenBrace);
        newName.AppendNum(iProbeCounter);
        _LIT8(KCloseBrace,")");
        newName.Append(KCloseBrace);
        iName.Insert(pos,newName); 
        ChangeDomainL(oldName);
        oldName.Close();
        __FLOG(_L8("CAdvertizeHandler::GenerateNonConflictingName- Exit"));
        return iProbeCounter;
    }

/*
 * Finds whether the service already exists in the network .
 * If service is in the network ,same will be present in the cache.
 * @return probFailed returns true if probe has failed.
 */
TBool CAdvertizeHandler::DefensiveResponseL()
    {
    __FLOG(_L8("CAdvertizeHandler::DefensiveResponseL- Entry"));
    TBool probeFailed(EFalse);      
    RPointerArray <CCacheEntry> entries;
    CleanupStack::PushL(TCleanupItem(TCleanupOperation(&CleanUpCache),&entries));
            
     _LIT8(KDot,".");
     TBuf8 <KMaxLength> buffer;  
        
     buffer.Copy(iName);
     buffer.Append(KDot);
     MessageHandler().DnsCache().FindServiceL(entries,buffer,EDnsQType_Any);   
     if(entries.Count()>0)
        {
        probeFailed = ETrue;
        }
     CleanupStack::PopAndDestroy();	
     //entries.ResetAndDestroy();
     //entries.Close();
     __FLOG(_L8("CAdvertizeHandler::DefensiveResponseL- Exit"));
     return probeFailed;
    }

/*
 * creates a DnsMessage object and send it to the messagehandler to handle it.
 * @param aUnicast true if the packet to be sent is an unicast one;false for multicast.
 * 
 */
void CAdvertizeHandler::SendProbeL(TBool aUnicast)
    {
    __FLOG(_L8("CAdvertizeHandler::SendProbeL- Entry"));
    //Construct DNS Message
    CDnsMessage* message = CDnsMessage::NewL(0,ETrue);
    CleanupStack::PushL(message);

    //Form the Query/Question part of the message
    CDnsQuestion* question = CDnsQuestion::NewL();
    CleanupStack::PushL(question);
    question->SetNameL(iName);
    question->SetClass(EDnsClass_IN);
    question->SetType(EDnsQType_Any);
    if(aUnicast)
        {
        question->SetUnicast(ETrue);
        }

    //Append the Query to the Message
    message->AppendQueryL(question);
    //Append to the Authoritative Section
    for(TInt i =0 ; i < iData.Count();i++)
        {
        message->AppendAuthorityL(iData[i]->CloneL());
        }
         
    //Send the query
    MessageHandler().SendQueryL(message,*this);

    CleanupStack::Pop();//question
    CleanupStack::Pop();//message
    __FLOG(_L8("CAdvertizeHandler::SendProbeL- Exit"));
    }

/*
 * If probing is successfull ,new service record will 
 * be added to the cache using this.
 */
void CAdvertizeHandler::InsertInCache()
    {
    __FLOG(_L8("CAdvertizeHandler::InsertInCache- Entry"));
    TBuf8<KMaxLength> name;
    _LIT8(KDot,".");
    //TODO name should be appended with dot
    
    for(TInt i =0 ; i<iData.Count();i++)
        {
        //Append  a "." at the end of the name
       name.Copy(iData[i]->Name());
       name.Append(KDot);  
       iData[i]->SetNameL(name);
       MessageHandler().DnsCache().UpdateCacheL(*(iData[i]),ETrue,iSessionId);
        }
    __FLOG(_L8("CAdvertizeHandler::InsertInCache- Exit"));   
    }

/*
 * Announces the new service.
 */
void CAdvertizeHandler::SendAnnouncementL()
    {
    __FLOG(_L8("CAdvertizeHandler::SendAnnouncementL- Entry"));
    RPointerArray<CCacheEntry> entries;
    //An API in Cache Interface that returns a list of all Authoritative records
    MessageHandler().DnsCache().AuthoritativeEntriesL(entries);
    CDnsMessage* message = CDnsMessage::NewL(0,EFalse);
    CleanupStack::PushL(message);  
    
    TDnsHeader header(message->Header());
    header.SetAuthoritative(ETrue);
    message->SetHeader(header);

    //Append to the Authoritative Section
    for(TInt i =0 ; i < iData.Count();i++)
        {
        message->AppendAuthorityL(iData[i]->CloneL());
        }
             
/*    
    for(TInt index =0; index<entries.Count();index++)
        {
        CCacheEntry* entry = entries[index];
        //Send Announcements for all the Services we have published, and entered in Cache           
            

        if(entry->AddressRecord())
            message->AppendAnswerL(entry->AddressRecord()->CloneL());
        if(entry->ServiceRecord())
            message->AppendAnswerL(entry->ServiceRecord()->CloneL());
        if(entry->PtrRecord())
            message->AppendAnswerL(entry->PtrRecord()->CloneL());
        if(entry->TxtRecord())
            message->AppendAnswerL(entry->TxtRecord()->CloneL());
               
        //should've been a new API sendresponse
                  
        }
*/        
    MessageHandler().SendQueryL(message,*this);
    CleanupStack::Pop();//message
    entries.ResetAndDestroy();
    entries.Close();
    __FLOG(_L8("CAdvertizeHandler::SendAnnouncementL- Exit"));
    }
void CAdvertizeHandler::ChangeDomainL(TDesC8& aName)
    {
    for(TInt i =0 ; i<iData.Count();i++)
        {
        if(iData[i]->Name().Compare(aName) == 0)
            {
            iData[i]->SetNameL(iName);
            }
        if(iData[i]->Type()==EDnsType_PTR)
            {
            CRdTypePtr* ptr = static_cast<CRdTypePtr*>(iData[i]);
            if(ptr->DomainName().Compare(aName) == 0)
                {
                ptr->SetDomainNameL(iName);
                }
            
            }
        
        }
    }
