
import * as Imap from "imap";
import * as Net from 'net'
import * as Async from "async";
import * as mailcomposer from "mailcomposer";
import * as mailparser from 'mailparser';
import * as shotrtID from 'shortid'
import * as Compress from './compress'

const idleInterval = 1000 * 60 * 60 * 24
const connectCheckTimeOut = 1000 * 15
const openBox = ( imap: startImap, mailBox, cb: ( err?: Error ) => void ) => {
	if ( imap.imap.state !== 'authenticated' ) {
		
		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 _sockWriteAppendData = appendData => {
  let val = appendData;
  if ( Buffer.isBuffer( appendData ))
    val = val.toString( 'utf8' );

  this.debug && this.debug ( '=> ' + inspect(val));
  this._sock.write ( val );
  this._sock.write ( CRLF );
  this.emit ( 'endSave' )
}
*/

const getImapConnect = ( nodeImap: IinputData, debug: boolean, forever: boolean ) => {

    const ret : IMAP.Config = {
        user: nodeImap.imapUserName,
        host: nodeImap.imapServer,
        password: nodeImap.imapUserPassword,
        port: parseInt ( nodeImap.imapPortNumber ),
		socketTimeout: 0,
		authTimeout: 25000,
		connTimeout:30000,
        tls: nodeImap.imapSsl,
        debug: debug 
            ? ( err ) => { console.log ( new Date(), ' = ', err )} 
            : null,
        keepalive: {
			idleInterval: idleInterval
		}
    }

    return ret;
}


export default class startImap {

	private destroy = false;
	private AUTHENTICATIONFAILED_error = false
	private fetching = false
	private fetchWait = false
	private busy = false
	private delayTime = null
	private idle = true
	private reserting = false
	private saveing = false
	public imap: IMAP.Connection = null
	private yyy: IMAP.Config = null
	private debug = true
	private Endprocess = false
	private lastNewMailID = null
	private savefunctioncallBack = null

	private scanEmail = ( imap: IMAP.Connection ) => {
		if ( this.imap.state !== 'authenticated' || this.busy ) {
			//console.log ( '=====> scanEmail cancel :', `listenFolder[${this.listeningFolder}],fetching[${this.fetching}], this.imap.state [${this.imap.state}]`)
			return this.fetchWait = true
		}

		this.fetching = this.busy =  true
		this.fetchWait = false
		
		const endFetch = () => {
			this.fetching = this.busy =  false
			if ( this.Endprocess ) {
				//console.log ('skip scan email Endprocess!')
				return this.endProcess ()
			}
				
			if ( this.fetchWait ) {
				//console.log ('this.fetchWait true, run scanEmail again!!!!', `listenFolder[${this.listeningFolder}]`)
				return this.scanEmail ( imap )
			}
				
		}

		return imap.search ( ['UNSEEN'], ( err, results ) => {

			if ( err ) {
				//console.log ( '=================================> imap.search error ', err )
				return endFetch ()
				
			}
			if ( ! results || ! results.length ) {
				//console.log ('no results ')
				return endFetch ()

			}
			
			if ( !this.lastNewMailID ) {
				this.lastNewMailID = parseInt ( results[0]) - 1
			}
			const startId = parseInt ( results[0])
			const stopId = parseInt ( results[results.length - 1] )
			if ( startId !== this.lastNewMailID + 1 ) {
				
				results = []
				for ( let i:number = this.lastNewMailID + 1; i <= stopId; i ++ ) {
					results.push ( i.toString ())
				}
			}
			this.lastNewMailID = stopId
			
			let fetch = imap.fetch ( results, { markSeen : true, bodies: '' });
			
			fetch.on ( 'message', ( msg, seqno ) => {

				let mp = new mailparser.MailParser();

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

						const buffer = msg.attachments [0].content
						this.debug ? console.log (`new mail {${buffer.length}}`) : null
						return this.newMailFunction ( buffer )
					}
					this.debug ? console.log ('get new mail but have not attachments') : null

				})
				
				msg.on ( 'body', ( data )  => {
					return data.pipe ( mp )
				})
				
			})

			fetch.once ( 'error', err1 => {
				console.log ( '=================================> imap.search fetch error:', err1 )
				return endFetch ()
			})

			fetch.once ( 'end', () => {
				return Async.series ([
					next => imap.addFlags ( results, ['\\Deleted'], next ),
					next => imap.expunge ( results, next )
				], () => {

					fetch.removeAllListeners ()
					fetch = null
					this.busy = false
					this.debug ? console.log (`new mail: [${results}]`) : null
					if ( this.fetchWait )
						return this.scanEmail ( imap )
					return endFetch ()
				})
				
			})

		})

		
	}

	private noopProcess () {
		if ( this.Endprocess || this.idle || !this.listeningFolder )
			return this.debug ? console.log (`[${this.Endprocess}][${this.idle}][${this.listeningFolder.length}]`) : null
		
		setTimeout (() => {
			
			if ( this.imap.state !== 'authenticated' || ! this.imap._queue || this.busy || this.imap._queue.length )
				return this.noopProcess()
			this.imap._enqueue ( 'NOOP', true )
			return this.noopProcess ()
		}, 10 + Math.random () * 90 )
	}

	private idleSupport () {
		this.idle = this.imap.serverSupports( 'IDLE' )
		if ( !this.idle ) {
			this.debug ? console.log ( 'idle not Support' ) : null
			return this.noopProcess ()
		}
	}


	public save = ( enCtypeMessage: Buffer|string , writeFolder: string, CallBack: ICallBack ) => {

		
		this.saveing = this.busy = true
		if ( CallBack && typeof CallBack === 'function')
			this.savefunctioncallBack = CallBack

		const socket: Net.Socket = this.imap._sock

		const email = mailcomposer (
			{
				date: ' ',
				messageId: shotrtID.generate(),
				attachments: [{
					filename: false,
					content: enCtypeMessage
				}]
			}
		)
		let TimeOut = null

		return Async.waterfall ([
			next => email.build ( next ),
			( _data: Buffer, next ) => {
				this.debug ? console.time ( `append{${ enCtypeMessage.length }}` ) : null
				TimeOut = setTimeout (() => {
					this.debug ? console.log ( `[${ enCtypeMessage.length }]`,'============================> save time out' ) : null
					this.imap.end()
					
				}, enCtypeMessage.length < 65536 ? 5000 : 15000 )	

				this.imap.append ( _data, { mailbox: writeFolder }, next )
			}
		], ( err, no ) => {
			clearTimeout ( TimeOut )
			this.saveing = this.busy = false
			this.savefunctioncallBack = null

			this.debug ? console.timeEnd( `append{${ enCtypeMessage.length }}`) : null
			this.debug ? console.log ( `[${ no }]` ) : null
			return CallBack( err );
		})
	}

	
	public checkBusy () {
		
		if ( !this.imap || this.imap.state !== 'authenticated' || ! this.imap._sock.writable || ! this.imap._sock.readable ) {
			this.endProcess ()
			return false
		}

		if ( this.imap._queue ) {
			if ( this.imap._queue.length )
				return false
			return this.busy = true
		}
		
	}

	public endProcess () {
		
		if ( this.Endprocess )
			return
		//console.log ('imap endProcess ', this.listeningFolder)
		this.Endprocess = true
		this.ready ( false )
		this.imap.end ()
		
		
		if ( this.savefunctioncallBack ) {
			
			this.savefunctioncallBack ( new Error ('socket end'))
		}

			
		if ( this.CallBack && typeof this.CallBack === 'function' )
				return this.CallBack ( this.AUTHENTICATIONFAILED_error ? new Error ( 'AUTHENTICATIONFAILED' ) : null )
			return process.exit ( this.AUTHENTICATIONFAILED_error ? 1: 0 )
	}

	private connectImap () {
		
		
		this.ready ( false )
		this.imap = new Imap ( this.yyy );
		this.destroy = this.fetching = this.AUTHENTICATIONFAILED_error = this.fetchWait = this.busy = false;

		this.imap.once ( 'ready', () => {
			this.reserting = false
			// this.imap._sockWriteAppendData = _sockWriteAppendData
			
			
			if ( this.listeningFolder && this.listeningFolder.length ) {
				
				return openBox ( this, this.listeningFolder, err => {
					clearTimeout ( this.delayTime )

					if ( err ) {
						this.debug ? console.log ( 'imap openBox error!' ) : null
						return this.imap.end ()
					}

					this.imap.on ( 'mail', mail => {

						return this.scanEmail ( this.imap )
					})
					
					this.idleSupport ()
					this.ready ( true )
					
				})
			}
			this.ready ( true )
			
			
		})
		
		this.imap.once ( 'error', ( err )  => {
			
			this.debug ? console.log ( 'this.imap.on ERROR',`listen:[${this.listeningFolder}]`, err.message ) : null
			
			this.AUTHENTICATIONFAILED_error = /AUTHENTICATIONFAILED/.test ( err.textCode )
			
			this.imap.end ()
			this.endProcess ()
		})
		
		this.imap.once ( 'end', () => {
			//console.log ( 'this.imap.on END',`listen:[${this.listeningFolder}], saveing:[${this.saveing}], busy:[${this.busy}]`)
			this.endProcess ()
		})

		return this.imap.connect ();

	}

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

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

