//
//  File:   hzPop3.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library 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 Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//
//  Method:         Contacts a POP3 server and retrieves emails.
//
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include "hzTextproc.h"
#include "hzDirectory.h"
#include "hzIpServer.h"
#include "hzTcpClient.h"
#include "hzProcess.h"
#include "hzCodec.h"
#include "hzMailer.h"
using namespace std ;
/*
**  Functions
*/
hzEcode hzPop3Acc::Init (hzString Server, hzString Username, hzString Password, hzString Repos)
{
    //  Initialize a POP3 Account that our application will access as client.
    //
    //  Arguments:  1)  Server      Server/Hostname to connect to.
    //              2)  Username    Username for the account.
    //              3)  Password    Password for the account.
    //              4)  Repos       Pathname for the email repository (of downloaded messages and headers)
    //
    //  Returns:    E_INITDUP   If this POP3 account manager has already been initialized
    //              E_ARGUMENT  If the server, username, password or the email repository pathname are not supplied 
    //              E_OK        If this POP3 account manager is now initialized
    hzVect  <hzString>  dirs ;      //  Vector of directories populated by ListDir()
    hzVect  <hzString>  files ;     //  Vector of files populated by ListDir()
    hzString    S ;             //  Temp string (filename)
    uint32_t    nIndex ;        //  Files iterator
    hzEcode     rc ;            //  Return code
    m_Error.Clear() ;
    m_Server = Server ;
    m_Username = Username ;
    m_Password = Password ;
    m_Repos = Repos ;
    if (m_Repos)
    {
        m_Error.Printf("hzPop3Acc::Init - Duplicate call\n") ;
        return E_INITDUP ;
    }
    if (!m_Server || !m_Username || !m_Password || !m_Repos)
    {
        m_Error.Printf("hzPop3Acc::Init\n") ;
        if (!m_Server)      m_Error.Printf(" - No POP3 server specified\n") ;
        if (!m_Username)    m_Error.Printf(" - No POP3 Username\n") ;
        if (!m_Password)    m_Error.Printf(" - No POP3 Password\n") ;
        if (!m_Repos)       m_Error.Printf(" - No POP3 Repository specified\n") ;
        return E_ARGUMENT ;
    }
    rc = AssertDir(Repos, 0777) ;
    if (rc != E_OK)
        return rc ;
    //  Read the repository and populate the m_Already map
    rc = ListDir(dirs, files, *Repos, 0) ;
    for (nIndex = 0 ; nIndex < files.Count() ; nIndex++)
    {
        S = files[nIndex] ;
        m_Already.Insert(S) ;
    }
    return E_OK ;
}
hzEcode hzPop3Acc::Collect  (hzVect<hzString>& messages)
{
    //  Collect emails from the POP3 Account (Conduct a POP3 session with the designated server). The emails are each placed in thier own file.
    //
    //  Arguments:  1)  messages    A vector of strings that is populated with filename of collected emails.
    //
    //  Returns:    E_NOINIT    The POP3 account is not initialized
    //              E_PROTOCOL  The session message not as expected
    //              E_OK        The email collection was successful
    _hzfunc("hzPop3Acc::Collect") ;
    hzMapS<int32_t,hzString>    temp ;  //  Emails available from the POP3 server
    ofstream        os ;            //  Output stream
    hzTcpClient     P ;             //  POP3 client connection
    chIter          zi ;            //  For iterating server responses
    hzChain         Z ;             //  For sending commands and receiving responses
    hzChain         word ;          //  For capturing small strings (eg mail number and mail id)
    hzChain         msg ;           //  For garnering messages
    hzString        S ;             //  For conversion of 'word' into a string
    hzString        path ;          //  For email filenames
    uint32_t        mailId ;        //  Mail number as given by server
    uint32_t        nIndex ;        //  For iterating avail emails
    hzEcode         rc ;            //  Return code
    m_Error.Clear() ;
    if (!m_Repos)
    {
        threadLog("POP3 Account not initialized\n") ;
        return E_NOINIT ;
    }
    rc = P.ConnectStd(m_Server, 110) ;
    if (rc != E_OK)
    {
        threadLog("Cannot connect to email server %s (error=%s)\n", *m_Server, Err2Txt(rc)) ;
        return rc ;
    }
    rc = P.SetSendTimeout(30) ;
    if (rc != E_OK)
    {
        threadLog("Could not set send_timeout on connection to POP3 server (error=%s)\n", Err2Txt(rc)) ;
        return rc ;
    }
    rc = P.SetRecvTimeout(30) ;
    if (rc != E_OK)
    {
        threadLog("Could not set recv_timeout on connection to POP3 server (error=%s)\n", Err2Txt(rc)) ;
        return rc ;
    }
    //  Expect the server to talk first with a +OK
    Z.Clear() ;
    rc = P.Recv(Z) ;
    if (rc != E_OK)
    {
        threadLog("Cannot recv server hello (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    S = Z ;
    threadLog("Server: [%s]\n", *S) ;
    zi = Z ;
    if (zi != "+OK")
    {
        rc = E_PROTOCOL ;
        threadLog("Expected +OK as hello from server. (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    //  Send the initial username
    Z.Clear() ;
    Z.Printf("USER %s\r\n", *m_Username) ;
    S = Z ;
    threadLog("Client: [%s]\n", *S) ;
    rc = P.Send(Z) ;
    if (rc != E_OK)
    {
        threadLog("Cannot send username to email server (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    //  Recv the +OK\r\n (to the username)
    Z.Clear() ;
    rc = P.Recv(Z) ;
    if (rc != E_OK)
    {
        threadLog("Cannot recv response to username from email server (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    S = Z ;
    threadLog("Server: [%s]\n", *S) ;
    zi = Z ;
    if (zi != "+OK")
    {
        rc = E_PROTOCOL ;
        S = Z ;
        threadLog("Expected +OK response to username (got=%s)\n", *S) ;
        goto done ;
    }
    //  Send the password
    Z.Clear() ;
    Z.Printf("PASS %s\r\n", *m_Password) ;
    S = Z ;
    threadLog("Client: [%s]\n", *S) ;
    rc = P.Send(Z) ;
    if (rc != E_OK)
    {
        threadLog("Cannot send password to email server (error=%s)\n", Err2Txt(rc)) ;
        rc = E_PROTOCOL ;
        goto done ;
    }
    //  Recv the +OK\r\n (to the password)
    Z.Clear() ;
    rc = P.Recv(Z) ;
    if (rc != E_OK)
    {
        threadLog("Cannot recv response to username from email server (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    S = Z ;
    threadLog("Server: [%s]\n", *S) ;
    zi = Z ;
    if (zi != "+OK")
    {
        S = Z ;
        threadLog("Expected +OK to password. (got=%s)\n", *S) ;
        rc = E_PROTOCOL ;
        goto done ;
    }
    //  Send the UIDL command
    Z.Clear() ;
    Z.Printf("UIDL\r\n") ;
    S = Z ;
    threadLog("Client: [%s]\n", *S) ;
    rc = P.Send(Z) ;
    if (rc != E_OK)
    {
        threadLog("Cannot send UIDL command to email server (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    //  Recv the +OK\r\n (to the UIDL command)
    Z.Clear() ;
    for (;;)    //nIndex = 0 ; nIndex < 3 ; nIndex++)
    {
        rc = P.Recv(Z) ;
        if (rc != E_OK)
        {
            threadLog("Cannot recv response to UIDL from email server (error=%s)\n", Err2Txt(rc)) ;
            goto done ;
        }
        threadLog("Server - Response to UIDL of %d bytes\n", Z.Size()) ;
        //  Test for the \r\n.\r\n
        zi = Z ;
        zi += (Z.Size() - 5) ;
        if (zi == "\r\n.\r\n")
            break ;
    }
    S = Z ;
    threadLog("Server: [%s]\n", *S) ;
    zi = Z ;
    if (zi != "+OK")
    {
        rc = E_PROTOCOL ;
        threadLog("Expected +OK response to UIDL (error=%s)\n", Err2Txt(rc)) ;
        goto done ;
    }
    //  Recieve the list of available messages and place them in the temporary map
    for (; !zi.eof() && *zi != CHAR_NL ; zi++) ;
    zi++ ;
    for (; !zi.eof() ; zi++)
    {
        for (; !zi.eof() && IsDigit(*zi) ; zi++)
            word.AddByte(*zi) ;
        if (!word.Size())
            break ;
        //zi.Skipwhite() ;
        S = word ;
        mailId = atoi(*S) ;
        word.Clear() ;
        for (zi++ ; !zi.eof() ; zi++)
        {
            if (*zi == CHAR_CR)
                zi++ ;
            if (*zi == CHAR_NL)
                break ;
            word.AddByte(*zi) ;
        }
        S = word ;
        word.Clear() ;
        if (!m_Already.Exists(S))
            temp.Insert(mailId, S) ;
        if (zi.eof())
            break ;
    }
    for (nIndex = 0 ; nIndex < temp.Count() ; nIndex++)
    {
        mailId = temp.GetKey(nIndex) ;
        S = temp.GetObj(nIndex) ;
        threadLog("Mailbox has %d %d (%s)\n", nIndex, mailId, *S) ;
    }
    //  Recv the email list. Some of these we may already have
    for (nIndex = 0 ; rc == E_OK && nIndex < temp.Count() ; nIndex++)
    {
        mailId = temp.GetKey(nIndex) ;
        S = temp.GetObj(nIndex) ;
        //  Send the RETR command
        Z.Clear() ;
        Z.Printf("RETR %d\r\n", mailId) ;
        rc = P.Send(Z) ;
        if (rc != E_OK)
            { threadLog("Cannot send RETR command to email server\n") ; break ; }
        //  Recv the +OK (to the RETR command). This involves repeated calls to Recv until the chain ends with a \r\n.\r\n sequence.
        Z.Clear() ;
        for (;;)
        {
            rc = P.Recv(Z) ;
            if (rc != E_OK)
                { threadLog("Cannot recv response to RETR command\n") ; break ; }
            threadLog("Server - Message of %d bytes\n", Z.Size()) ;
            //  Test for the \r\n.\r\n
            zi = Z ;
            zi += (Z.Size() - 5) ;
            if (zi == "\r\n.\r\n")
                break ;
        }
        //  Go to end of line, then message body, the the CR/NL period CR/NL sequence
        msg.Clear() ;
        zi = Z ;
        if (zi != "+OK")
        {
            rc = E_OK ;
            S = Z ;
            threadLog("Expected +OK in response to RETR command (got=%s)\n", *S) ;
            continue ;
        }
        for (zi += 3 ; !zi.eof() && *zi != CHAR_NL ; zi++) ;
        if (*zi != CHAR_NL)
        {
            threadLog("Malformed +OK response to RETR command\n") ;
            continue ;
        }
        for (zi++ ; !zi.eof() ; zi++)
        {
            if (*zi == CHAR_CR)
            {
                if (zi == "\r\n.\r\n")
                {
                    zi += 5 ;
                    if (zi.eof())
                        break ;
                    zi -= 5 ;
                }
            }
            msg.AddByte(*zi) ;
        }
        path = m_Repos + "/" + S ;
        os.open(*path) ;
        if (os.fail())
            threadLog("Cannot open file %s for writting\n", *path) ;
        else
        {
            threadLog("Writing message file %s\n", *path) ;
            os << msg ;
            os.close() ;
            os.clear() ;
            messages.Add(S) ;
        }
    }
    os.close() ;
done:
    P.Close() ;
    return rc ;
}
hzEcode hzPop3Acc::GetEmail (hzEmail& theMessage, hzString& mailId)
{
    //  Go to the archive file for emails (one produced each day), and retrieve the email header and body for the given id
    //
    //  Arguments:  1)  em      The email instance populated by this operation
    //              2)  mailId  The email unique id
    //
    //  Returns:    E_OPENFAIL  If the mailbox file could not be opened.
    //              E_FORMAT    If the email is malformed
    //              E_OK        If the email was successfully retrieved from the mailbox
    _hzfunc("hzPop3Acc::GetEmail") ;
    hzList<hzEmpart>::Iter  pi ;    //  Iterator for the email parts
    ifstream    is ;                //  Input stream
    hzChain     Z ;                 //  For loading message
    hzString    filepath ;          //  Full path to email file
    hzEcode     rc ;                //  Return code
    m_Error.Clear() ;
    theMessage.Clear() ;
    //  Open email file and read in the header. This starts with a line of the form 'From email_addr date' and is followed by lines of
    //  the form 'param: args'. These lines may be followed by continuation lines that begin with whitespace. We read in continuation
    //  lines as though they are just extensions to the preceeding parameter line.
    filepath = m_Repos + "/" + mailId ;
    rc = OpenInputStrm(is, filepath) ;
    if (rc != E_OK)
    {
        hzerr(E_OPENFAIL, "Failed tp open email file %s", *filepath) ;
        return E_OPENFAIL ;
    }
    Z << is ;
    is.close() ;
    rc = theMessage.Import(Z) ;
    return rc ;
}