#include "hostsutils.h"
#include "dnsgenerator.h"
#include "goodiplist.h"

#define CONTEXT_DATA_LENGTH 2048

static int HostsUtils_GetCName_Callback(int Number,
                                        HostsRecordType Type,
                                        const char *Data,
                                        char *Buffer
                                        )
{
    strcpy(Buffer, Data);
    return 0;
}

int HostsUtils_GetCName(const char *Domain,
                        char *Buffer,
                        HostsContainer *Container
                        )
{
    return Container->Find(Container,
                           Domain,
                           HOSTS_TYPE_CNAME,
                           (HostsFindFunc)HostsUtils_GetCName_Callback,
                           Buffer
                           )
            == NULL;
}

BOOL HostsUtils_TypeExisting(HostsContainer *Container,
                             const char *Domain,
                             HostsRecordType Type
                             )
{
    return Container->Find(Container,
                           Domain,
                           Type,
                           NULL,
                           NULL
                           )
            != NULL;
}

static int HostsUtils_Generate(int              Number,
                               HostsRecordType  Type,
                               const void       *Data,
                               DnsGenerator     *g /* Inited */
                               )
{
    switch( Type )
    {
    case HOSTS_TYPE_CNAME:
        if( g->CName(g, "a", Data, 60) != 0 )
        {
            return -26;
        }
        break;

    case HOSTS_TYPE_A:
        if( g->RawData(g,
                       "a",
                       DNS_TYPE_A,
                       DNS_CLASS_IN,
                       Data,
                       4,
                       60
                       )
            != 0 )
        {
            return -41;
        }
        break;

    case HOSTS_TYPE_AAAA:
        if( g->RawData(g,
                       "a",
                       DNS_TYPE_AAAA,
                       DNS_CLASS_IN,
                       Data,
                       16,
                       60
                       )
            != 0 )
        {
            return -56;
        }
        break;

    case HOSTS_TYPE_GOOD_IP_LIST:
        {
            const char *ActuallData;

            ActuallData = GoodIpList_Get(Data);
            if( ActuallData == NULL )
            {
                return -96;
            }

            if( g->RawData(g,
                           "a",
                           DNS_TYPE_A,
                           DNS_CLASS_IN,
                           ActuallData,
                           4,
                           60
                           )
                != 0 )
            {
                return -109;
            }
        }
        break;

    default:
        return -61;
        break;
    }

    return 0;
}

HostsUtilsTryResult HostsUtils_Try(MsgContext *MsgCtx,
                                   int BufferLength,
                                   HostsContainer *Container
                                   )
{
    IHeader *Header = (IHeader *)MsgCtx;
    char *RequestEntity = (char *)(Header + 1);
    const char  *MatchState;
    HostsRecordType Type;

    if( Header->Type != DNS_TYPE_CNAME &&
        HostsUtils_TypeExisting(Container, Header->Domain, HOSTS_TYPE_CNAME)
        )
    {
        return HOSTSUTILS_TRY_RECURSED;
    }

    switch( Header->Type )
    {
    case DNS_TYPE_CNAME:
        Type = HOSTS_TYPE_CNAME;
        MatchState = Container->Find(Container,
                                     Header->Domain,
                                     HOSTS_TYPE_CNAME,
                                     NULL,
                                     NULL
                                     );
        break;

    case DNS_TYPE_A:
        MatchState = Container->Find(Container,
                                     Header->Domain,
                                     HOSTS_TYPE_A,
                                     NULL,
                                     NULL
                                     );
        if( MatchState != NULL )
        {
            Type = HOSTS_TYPE_A;
        } else {
            Type = HOSTS_TYPE_GOOD_IP_LIST;
            MatchState = Container->Find(Container,
                                         Header->Domain,
                                         HOSTS_TYPE_GOOD_IP_LIST,
                                         NULL,
                                         NULL
                                         );
        }
        break;

    case DNS_TYPE_AAAA:
        Type = HOSTS_TYPE_AAAA;
        MatchState = Container->Find(Container,
                                     Header->Domain,
                                     HOSTS_TYPE_AAAA,
                                     NULL,
                                     NULL
                                     );
        break;

    default:
        return HOSTSUTILS_TRY_NONE;
        break;
    }

    if( MatchState != NULL )
    {
        DnsGenerator g;

        char *HereToGenerate = RequestEntity + Header->EntityLength;
        int LeftBufferLength =
                          BufferLength - sizeof(IHeader) - Header->EntityLength;

        int ResultLength;

        if( DnsGenerator_Init(&g,
                              HereToGenerate,
                              LeftBufferLength,
                              RequestEntity,
                              Header->EntityLength,
                              TRUE
                              )
           != 0)
        {
            return HOSTSUTILS_TRY_NONE;
        }

        g.Header->Flags.Direction = 1;
        g.Header->Flags.AuthoritativeAnswer = 0;
        g.Header->Flags.RecursionAvailable = 1;
        g.Header->Flags.ResponseCode = 0;
        g.Header->Flags.Type = 0;

        if( g.NextPurpose(&g) != DNS_RECORD_PURPOSE_ANSWER )
        {
            return HOSTSUTILS_TRY_NONE;
        }

        if( Container->Find(Container,
                            Header->Domain,
                            Type,
                            (HostsFindFunc)HostsUtils_Generate,
                            &g
                            )
            == NULL )
        {
            return HOSTSUTILS_TRY_NONE;
        }

        if( Header->EDNSEnabled )
        {
            while( g.NextPurpose(&g) != DNS_RECORD_PURPOSE_ADDITIONAL );
            if( g.EDns(&g, 1280) != 0 )
            {
                return HOSTSUTILS_TRY_NONE;
            }
        }

        /* g will no longer be needed, and can be crapped */
        ResultLength = DNSCompress(HereToGenerate, g.Length(&g));
        if( ResultLength < 0 )
        {
            return HOSTSUTILS_TRY_NONE;
        }

        Header->EntityLength = ResultLength;
        memmove(RequestEntity, HereToGenerate, ResultLength);

        MsgContext_SendBack(MsgCtx);

        return HOSTSUTILS_TRY_OK;
    } else {
        return HOSTSUTILS_TRY_NONE;
    }
}

int HostsUtils_GenerateQuery(char           *RequestBuffer,
                             int            BufferLength,
                             SOCKET         Socket, /* Both for sending and receiving */
                             Address_Type   *BackAddress,
                             BOOL           RequestTcp,
                             uint16_t       Identifier,
                             const char     *Name,
                             DNSRecordType  Type
                             )
{
    static const char DNSHeader[DNS_HEADER_LENGTH] = {
        00, 00, /* QueryIdentifier */
        01, 00, /* Flags */
        00, 00, /* QuestionCount */
        00, 00, /* AnswerCount */
        00, 00, /* NameServerCount */
        00, 00, /* AdditionalCount */
    };

    IHeader *Header = (IHeader *)RequestBuffer;
    char *RequestEntity = RequestBuffer + sizeof(IHeader);

    DnsGenerator g;

    if( DnsGenerator_Init(&g,
                          RequestEntity,
                          BufferLength - sizeof(IHeader),
                          DNSHeader,
                          DNS_HEADER_LENGTH,
                          FALSE
                          )
        != 0 )
    {
        return -323;
    }

    g.SetIdentifier(&g, Identifier);

    if( g.Question(&g, Name, Type, DNS_CLASS_IN) != 0 )
    {
        return -328;
    }

    if( IHeader_Fill(Header,
                     TRUE,
                     RequestEntity,
                     g.Length(&g),
                     (struct sockaddr *)&(BackAddress->Addr),
                     Socket,
                     BackAddress->family,
                     "CNameRedirect"
                     )
        != 0 )
    {
        return -309;
    }

    Header->RequestTcp = RequestTcp;

    return 0;
}

/* Error code returned */
int HostsUtils_CombineRecursedResponse(MsgContext   *Buffer,
                                       int          Bufferlength,
                                       char         *RecursedEntity,
                                       int          EntityLength,
                                       const char   *RecursedDomain
                                       )
{
    IHeader *NewHeader = (IHeader *)Buffer;
    char *NewEntity = (char *)(NewHeader + 1);

    DnsSimpleParser p;
    DnsSimpleParserIterator i;
    DnsGenerator g;

    uint16_t OriginalIdentifier = DNSGetQueryIdentifier(NewEntity);

    int CompressedLength;

    if( DnsSimpleParser_Init(&p,
                             RecursedEntity,
                             EntityLength,
                             FALSE
                             ) != 0
        )
    {
        return -268;
    }

    if( DnsSimpleParserIterator_Init(&i, &p) != 0 )
    {
        return -273;
    }

    if( DnsGenerator_Init(&g,
                          NewEntity,
                          Bufferlength - sizeof(IHeader),
                          NULL,
                          0,
                          FALSE
                          )
       != 0 )
    {
        return -285;
    }

    g.CopyHeader(&g, RecursedEntity, FALSE);
    g.SetIdentifier(&g, OriginalIdentifier);

    if( g.Question(&g,
                   NewHeader->Domain,
                   NewHeader->Type,
                   DNS_CLASS_IN
                   )
       != 0 )
    {
        return -298;
    }

    if( g.NextPurpose(&g) != DNS_RECORD_PURPOSE_ANSWER )
    {
        return -303;
    }

    if( g.CName(&g, NewHeader->Domain, RecursedDomain, 60) != 0 )
    {
        return -309;
    }

    i.GotoAnswers(&i);

    while( i.Next(&i) != NULL && i.Purpose == DNS_RECORD_PURPOSE_ANSWER )
    {
        switch( i.Type )
        {
        case DNS_TYPE_CNAME:
            if( g.CopyCName(&g, &i) != 0 )
            {
                return -321;
            }
            break;

        case DNS_TYPE_A:
            if( g.CopyA(&g, &i) != 0 )
            {
                return -328;
            }
            break;

        case DNS_TYPE_AAAA:
            if( g.CopyAAAA(&g, &i) != 0 )
            {
                return -335;
            }
            break;

        default:
            break;
        }
    }

    if( NewHeader->EDNSEnabled )
    {
        while( g.NextPurpose(&g) != DNS_RECORD_PURPOSE_ADDITIONAL );

        if( g.EDns(&g, 1280) != 0 )
        {
            return -351;
        }
    }

    CompressedLength = DNSCompress(g.Buffer, g.Length(&g));
    if( CompressedLength < 0 )
    {
        return -343;
    }

    NewHeader->EntityLength = CompressedLength;

    return 0;
}
