import * as IMAP from 'imap';
import * as event from 'events';
import * as mailparser from 'mailparser';
import * as async from 'async';
import * as smtpClass from './smtpClass';

interface nodeImap {
    imap_account: string;
    imap_password: string;
}

const Mailparser = mailparser.MailParser;

/**
 *  @param imap {IMAP.Connection} imap Connection.
 *  @param mailBox {string} mail box name that want open
 *  @cb {function} call back function
 */
const openInbox = ( imap, mailBox: string, cb: ( err?: Error, box? ) => void ) => {
    imap.openBox ( mailBox, false, err => {
        
        if ( err ) {
            imap.addBox ( mailBox, err1 => {
                if ( err1 )
                    return cb ( err1 );
                return openInbox ( imap, mailBox, cb );
            })
        }
        cb ();
    })
}
/**
 *  @param imap {IMAP.Connection}
 *  @param clearBox {boolean}
 *  @param events {event.EventEmitter}
 * Decryption (msg.attachments[0].content.toString('utf8')  , key.password, key.private, service.public_key, ( err, data ) => {
 * data.signatures[0].valid
 */
const scanEmail = ( imap, clearBox = false, events: event.EventEmitter, CallBack: ICallBack = null ) => {
    
    async.waterfall ([ next => {
        
        imap.search ( [ 'UNSEEN' ], next );
        
    }, ( results, next ) => {
        
        if ( !results || !results.length )
            return next ();
            
        const fetch = imap.fetch ( results, { markSeen : true, bodies: [ '' ]});
        
        let ret = [];
        
        fetch.on ( 'message', msg => {
            
            let mp = new Mailparser ();
            
            let buffer = '';
                
            mp.once ( 'end',  msg => {
                
                events.emit ( 'email', msg );
                
            })

            msg.on ( 'body', data => {
                
                data.on ('data', ( chunk ) => {
                    
                    buffer += chunk.toString ()
                    
                })
            })
            
            msg.once ( 'end', () => {
                
                mp.write ( buffer );
                
                mp.end ()
                
            })
            
        })
        
        fetch.once ( 'error', err => {
            
            next ( err )
            
        })
        
        fetch.once ( 'end', () => {
            
            if ( clearBox )
                return imap.addFlags ( results, [ '\\Deleted' ], next );
                
            next ( null );
            
        })
       
        
    }], err => {
        
        if ( err ) return events.emit ( 'error', err );
        if ( CallBack && typeof CallBack === 'function' )
            CallBack ()
        
    })
}
/***
 *  @param user {string} imap user name.
 *  @param password {string} imap user password
 *  @param mailBoxName {string} open mail box name
 *  @param forever {boolean} if forever true
 * 
 *  event emit ***
 *  @param error {Error} when err
 *  @param email {mailparser.ParsedMail []} new email data
 *  
 *  event listen ***
 *  @param save
 */
export default class imapConnect {
    
    private _imapConnects: IMAP.Connection;
    
    static type = 'class imapConnect ';

    public _events = new event.EventEmitter ();
    
    private imapEnd = false;
    
    private haveError = false;
    
    public openFolder = false;
    
    private CallBack = null;
    
    constructor ( private user: string, private password: string, private host: string, private port: number,
        private tls: boolean, private newMailCheck: boolean, private mailBoxName,
        private clearBox:boolean, private forever: boolean, private isTest: boolean, private debug: boolean) {

        const ImapConnectConfig = {
            
            user: user,
            host: host,
            password: password,
            port: port,
            tls: tls,
            debug: debug 
                ? ( err ) => { console.log ( new Date(), ' = ', err )} 
                : null,
            
            keepalive:  true
        }
        
        this.imapEnd = !forever
        
        this._imapConnects = new IMAP ( ImapConnectConfig )
        
        if ( ! isTest )
            this.connect ()
        
    }
    
    public connect ( CallBack : ICallBack = null ) {
        this.CallBack = CallBack;
        this._imapConnects.once ( 'ready', () => {

            if ( this.mailBoxName ) {
                
                const loop = () => {
                    
                     if ( this._imapConnects.state !== 'authenticated' ) {
                            return setTimeout (() => {
                                loop ()
                            }, 1000 );
                     }
                     
                     openInbox ( this._imapConnects, this.mailBoxName, ( err, box ) => {
                
                        if ( err ) {
                            
                            console.log ( err.message );

                        }
                        
                        console.log ( 'watch mail box ready:', this.mailBoxName )
                        this.openFolder = true;
                        this._events.emit ( 'imapReady' )
                        
                        if ( this.imapEnd ) {

                            this._imapConnects.destroy ();
                        }
                        
                        if ( CallBack && typeof CallBack === 'function' ) {
                            CallBack ( null, this )
                        }
                    })
                }
                
                loop();      
            }
        })
        

        this._imapConnects.on ( 'mail', () => {
            if ( this.newMailCheck ) {
                scanEmail ( this._imapConnects, this.clearBox, this._events );
            }
        })

        this._events.once ( 'end', () => {
            //console.log ('this._imapConnects.once end')
            this._imapConnects.destroy ();
            this._events.emit ( 'end' );
        })

        
        this._imapConnects.once ( 'error', ( err ) => {
            console.log ('this._imapConnects.once error')
            if ( this.forever ) {
                this._imapConnects.connect ();
            }
            this._events.emit ( 'error', err );
        })

        this._imapConnects.connect ();

    }
    
    
    public save ( boxName:string, email: mailcomposer, CallBack: ICallBack ) {
    

        if ( this._imapConnects.state !== 'authenticated' ) {
            return setTimeout (() => {
                this.save ( boxName, email, CallBack )
            }, 2000 );
        }
        console.log ( 'save message to folder', boxName )
        async.waterfall ([
            
            next => {
                
                email.build ( next );
                
            }, ( data, next ) => {
                
                this._imapConnects.append ( data, { mailbox: boxName }, next );
                
            }
            
        ], ( err, data ) => {
            
            if ( err ) {
            
                return this._imapConnects.addBox ( boxName, err => {
                    
                    if ( err ) {
                        
                        return CallBack ( err );

                    }
                    
                    this.save ( boxName, email, CallBack );
                    
                })
                
            }
            if ( !this.forever ) {
                
                this._imapConnects.end ();
                
            }
            CallBack ()
            
        })
        
    }

    public distroy () {
        this._imapConnects.removeAllListeners()
        this._imapConnects.delBox ( this.mailBoxName, () => {
            this._imapConnects.destroy ()
         })
        
    }
}

