
import * as Imap from "imap";
import * as Async from "async";
import * as mailcomposer from "mailcomposer";
import * as mailparser from 'mailparser';
import * as shortID from 'shortid'
import * as Net from 'net'
import * as Stream from 'stream'

const idleInterval = 1000 * 60 * 60 * 24

const openBox = ( imap: startImap, mailBox, cb: ( err?: Error ) => void ) => {
	if ( imap.imap.state !== 'authenticated' ) {
		console.log (imap.imap.state)
		return setTimeout (() => {
			openBox ( imap, mailBox, cb )
		}, 1000 )
	}


	imap.imap.openBox ( mailBox, false, err => {
		if ( err ) {
			return imap.imap.addBox ( mailBox, err1 => {
				if ( err1 )
					return cb ( err )
				openBox ( imap, mailBox, cb )
			})
		}
		return cb ()
	})

}

const getImapConnect = ( nodeImap: IinputData, debug: boolean, forever: boolean ) => {
	const keepalive = ( /outlook.com$|/.test (nodeImap.imapServer))
    const ret : IMAP.Config = {
        user: nodeImap.imapUserName,
        host: nodeImap.imapServer,
        password: nodeImap.imapUserPassword,
        port: parseInt ( nodeImap.imapPortNumber ),
        tls: nodeImap.imapSsl,
        debug: debug 
            ? ( err ) => { console.log ( new Date(), ' = ', err )} 
            : null,
        keepalive: {
			interval: 400,
			idleInterval: idleInterval
			,forceNoop: true
		}
		
    }

    return ret;
}

export default class startImap {
	private lastNewMail = new Date()
	private checkBusy = null
	private destroy = false;
	private reConnectCount = 0
	private connected = false
	private fetching = false
	private fetchWait = false
	private busy = false
	private delayTime = null

	private scanEmail = ( imap: IMAP.Connection ) => {
		this.fetching = true
		this.fetchWait = false
		return imap.search ( ['UNSEEN'], ( err, results ) => {
			if ( err ) {

				console.log ( 'imap.search error ')
				if ( this.fetchWait )
					return this.scanEmail ( imap )
				return this.fetching = false
				
			}
				
			if ( !results || ! results.length ) {
				if ( this.fetchWait )
					return this.scanEmail ( imap )
				return this.fetching = false
				
			}
				
			let fetch = imap.fetch ( results, { markSeen : true, bodies: ''});

			fetch.on ( 'message', msg => {

				let mp = new mailparser.MailParser();

			
				mp.once ( 'end', ( msg: mailparser.ParsedMail ) => {
					
					if ( msg.attachments && msg.attachments.length ) {

						return this.newMailFunction ( msg.attachments [0].content )
					}
					/*
					const interval = 100 + Math.random () * 200
					this.imap._config.keepalive.interval = interval
					console.log ( 'have not attachments' )
					*/
				})
				
				msg.on ( 'body', ( data: Stream.Readable )  => {
					return data.pipe (mp)
				})

				msg.once ( 'end', () => {
					
				})
				
			})
			fetch.once ( 'error', err => {
				this.fetching = this.busy = false
				fetch.removeAllListeners ()
				fetch = null
				return console.log ( this.imapAcc.imapUserName,' fetch error ', err )
				
			})

			fetch.once ( 'end', () => {
				return Async.series ([
					next => imap.addFlags ( results, ['\\Deleted'], next ),
					next => imap.expunge ( results, next )
				], err => {
					fetch.removeAllListeners ()
					fetch = null
					//console.log ( 'fetch.once END!!' )
					if ( this.fetchWait )
						return this.scanEmail ( imap )
					
					return this.busy = this.fetching = this.fetchWait = false
					/*
					this.lastNewMail = new Date ()
					const interval = 100 + Math.random () * 200
					this.imap._config.keepalive.interval = interval
					//console.log ( ' fetch end set new interval = ', interval )
					*/
				})
				
			})
		})
	}

	public imap: IMAP.Connection = null;
	

	public save = ( enCtypeMessage: Buffer , writeFolder: string, CallBack: ICallBack ) => {
		if ( this.destroy )
			return

		if ( !this.connected ) {
			
			return  setTimeout (() => {
				this.save ( enCtypeMessage, writeFolder, CallBack );
			}, 1000 );
		}
		this.busy = true
		
		const email = mailcomposer (
			{
				attachments: [{
					filename: false,
					content: enCtypeMessage
				}]
			}
		)

		return Async.waterfall ([
			next => email.build ( next ),
			( data: Buffer, next ) => {
				//console.log (`${ new Date().toISOString() }===========>[${jj.uuid}][${jj.serial}]->[${jj.command}][${jj.buffer.toString('hex',0,30)}]`)
				this.imap.append ( data, { mailbox: writeFolder }, next )
			}
		], err => {
			this.busy = false
			if ( err ) {
				console.log ('imap save got error!', err.message )
				return CallBack ( err )
			}
			return CallBack ()
		})
		
	}

	public destroyImap () {
		if ( this.busy ) {
			console.log ('this.busy destroy wait 5 seconds!', this.listeningFolder)
			return setTimeout(() => {
				this.destroyImap ()
			}, 5000 );
		}
			
		this.destroy = true

		if ( !this.delBox ) {
			return this.imap.end ()
		}

		this.imap.delBox ( this.listeningFolder, () => {
			return this.imap.end ()
		})
			
	}
	
	private connectImap ( yyy: IMAP.Config ) {

		this.imap = new Imap ( yyy );
		
		this.imap.once ( 'ready', err => {
			
			return openBox ( this, this.listeningFolder, err => {
				
				if ( err ) {
					this.destroyImap()
				}
				this.delayTime = new Date ().getTime () - this.delayTime
				this.connected = true

			})
		})

		
		this.imap.on ( 'mail', mail => {
			this.busy = true
			if ( this.fetching )
				return this.fetchWait = true
			return this.scanEmail ( this.imap )
		})
		
		this.imap.on ( 'error', ( err: Error )  => {
			console.log ('err!', err.message)
			if ( this.reConnect || /^Timed out while authenticating with server$|ECONNRESET/.test( err.message ))
				return setTimeout (() => {

					if ( this.imap && this.imap.connect ) {
						console.log ('try re connect')
						return this.imap.connect ()
					}
					console.log ('dont try again')
					this.destroy = true
					return this.destroyImap ()
				}, 3000 )

			console.log ( 'this.imap.on error, stop imap')
			this.destroy = true
			this.imap.removeAllListeners()
			this.imap.destroy ()
			return this.CallBack ( err )

		});
		
		this.imap.once ( 'end', () => {
			
			if ( this.reConnect && ! this.destroy ) {
				return this.connectImap ( yyy )
			}
			this.imap.removeAllListeners ()
			this.imap = null
			return this.CallBack ( null, this.delayTime )
		});
		this.delayTime = new Date().getTime()
		return this.imap.connect ();

	}

	constructor ( private imapAcc: IinputData, private listeningFolder: string, private reConnect: boolean, private delBox: boolean, 
		private newMailFunction: ( msg: Buffer ) => void, private CallBack: ICallBack ) {

		const yyy = getImapConnect ( imapAcc, false, true );
		this.connectImap ( yyy )
	}
}