/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License. 
 *  
 */
package org.apache.directory.server.dns.service;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.directory.server.dns.DnsException;
import org.apache.directory.server.dns.messages.DnsMessage;
import org.apache.directory.server.dns.messages.DnsMessageModifier;
import org.apache.directory.server.dns.messages.MessageType;
import org.apache.directory.server.dns.messages.OpCode;
import org.apache.directory.server.dns.messages.QuestionRecord;
import org.apache.directory.server.dns.messages.ResourceRecord;
import org.apache.directory.server.dns.messages.ResponseCode;
import org.apache.directory.server.dns.store.RecordStore;
import org.apache.directory.server.i18n.I18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Domain Name Service (DNS) Protocol (RFC 1034, 1035)
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public final class DomainNameService
{
    /** the log for this class */
    private static final Logger LOG = LoggerFactory.getLogger( DomainNameService.class );


    private DomainNameService()
    {
    }


    /**
     * Creates a new instance of DomainNameService.
     */
    public static void execute( DnsContext dnsContext, DnsMessage request ) throws Exception
    {
        if ( LOG.isDebugEnabled() )
        {
            monitorRequest( request );
        }

        getResourceRecords( dnsContext, request );

        if ( LOG.isDebugEnabled() )
        {
            monitorContext( dnsContext );
        }

        buildReply( dnsContext, request );

        if ( LOG.isDebugEnabled() )
        {
            monitorReply( dnsContext );
        }
    }


    private static void monitorRequest( DnsMessage request )
    {
        try
        {
            if ( LOG.isDebugEnabled() )
            {
                LOG.debug( monitorMessage( request, "request" ) );
            }
        }
        catch ( Exception e )
        {
            // This is a monitor.  No exceptions should bubble up.
            LOG.error( I18n.err( I18n.ERR_153 ), e );
        }
    }


    private static void getResourceRecords( DnsContext dnsContext, DnsMessage request ) throws DnsException
    {
        RecordStore store = dnsContext.getStore();

        List<QuestionRecord> questions = request.getQuestionRecords();

        Iterator<QuestionRecord> it = questions.iterator();

        while ( it.hasNext() )
        {
            dnsContext.addResourceRecords( getEntry( store, it.next() ) );
        }
    }


    /**
     * Returns a set of {@link ResourceRecord}s from a {@link RecordStore}, given a DNS {@link QuestionRecord}.
     *
     * @param store
     * @param question
     * @return The set of {@link ResourceRecord}s.
     * @throws DNSException
     */
    private static Set<ResourceRecord> getEntry( RecordStore store, QuestionRecord question ) throws DnsException
    {
        Set<ResourceRecord> records = null;

        records = store.getRecords( question );

        if ( records == null || records.isEmpty() )
        {
            LOG.debug( "The domain name referenced in the query does not exist." );

            throw new DnsException( ResponseCode.NAME_ERROR );
        }

        return records;
    }


    private static void monitorContext( DnsContext dnsContext )
    {
        try
        {
            RecordStore store = dnsContext.getStore();
            List<ResourceRecord> records = dnsContext.getResourceRecords();

            StringBuilder sb = new StringBuilder();
            sb.append( "Monitoring context:" );
            sb.append( "\n\t" + "store:                     " + store );
            sb.append( "\n\t" + "records:                   " + records );

            LOG.debug( sb.toString() );
        }
        catch ( Exception e )
        {
            // This is a monitor.  No exceptions should bubble up.
            LOG.error( I18n.err( I18n.ERR_154 ), e );
        }
    }


    private static void buildReply( DnsContext dnsContext, DnsMessage request ) 
    {
        List<ResourceRecord> records = dnsContext.getResourceRecords();

        DnsMessageModifier modifier = new DnsMessageModifier();

        modifier.setTransactionId( request.getTransactionId() );
        modifier.setMessageType( MessageType.RESPONSE );
        modifier.setOpCode( OpCode.QUERY );
        modifier.setAuthoritativeAnswer( false );
        modifier.setTruncated( false );
        modifier.setRecursionDesired( request.isRecursionDesired() );
        modifier.setRecursionAvailable( false );
        modifier.setReserved( false );
        modifier.setAcceptNonAuthenticatedData( false );
        modifier.setResponseCode( ResponseCode.NO_ERROR );
        modifier.setQuestionRecords( request.getQuestionRecords() );

        modifier.setAnswerRecords( records );
        modifier.setAuthorityRecords( new ArrayList<ResourceRecord>() );
        modifier.setAdditionalRecords( new ArrayList<ResourceRecord>() );

        dnsContext.setReply( modifier.getDnsMessage() );
    }


    private static void monitorReply( DnsContext dnsContext )
    {
        try
        {
            DnsMessage reply = dnsContext.getReply();

            if ( LOG.isDebugEnabled() )
            {
                LOG.debug( monitorMessage( reply, "reply" ) );
            }
        }
        catch ( Exception e )
        {
            // This is a monitor.  No exceptions should bubble up.
            LOG.error( I18n.err( I18n.ERR_155 ), e );
        }
    }


    private static String monitorMessage( DnsMessage message, String direction )
    {
        MessageType messageType = message.getMessageType();
        OpCode opCode = message.getOpCode();
        ResponseCode responseCode = message.getResponseCode();
        int transactionId = message.getTransactionId();

        StringBuilder sb = new StringBuilder();
        sb.append( "Monitoring " + direction + ":" );
        sb.append( "\n\t" + "messageType                " + messageType );
        sb.append( "\n\t" + "opCode                     " + opCode );
        sb.append( "\n\t" + "responseCode               " + responseCode );
        sb.append( "\n\t" + "transactionId              " + transactionId );

        sb.append( "\n\t" + "authoritativeAnswer        " + message.isAuthoritativeAnswer() );
        sb.append( "\n\t" + "truncated                  " + message.isTruncated() );
        sb.append( "\n\t" + "recursionDesired           " + message.isRecursionDesired() );
        sb.append( "\n\t" + "recursionAvailable         " + message.isRecursionAvailable() );
        sb.append( "\n\t" + "reserved                   " + message.isReserved() );
        sb.append( "\n\t" + "acceptNonAuthenticatedData " + message.isAcceptNonAuthenticatedData() );

        List<QuestionRecord> questions = message.getQuestionRecords();

        sb.append( "\n\t" + "questions:                 " + questions );

        return sb.toString();
    }
}
