/*!
 * Copyright 2017 Vpn.Email network security technology Canada Inc. All Rights Reserved.
 *
 * Vpn.Email network technolog Canada Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import * as Async from 'async'
import imapConnect from './class_imapConnect'
import * as Stream from 'stream'
import * as Compress from './compress'
import * as execImap from './execImap'

export default class imapCluster  {

	private mainImapPool: execImap.VE_IimapPool [] = []
	private subImapPool:execImap.VE_IimapPool [] = []
	private masterImap: imapConnect[] = []
	private dataPool: ISaveDataPool[] = []
	private endcall = false
	private mainImapPoint = 0
	private aliasImapPoint = 0
	private maindataPool: Buffer[] = []
	private CallBackPool: Map < string, () => void > = new Map ()
	private count = 0

	private findIdleImap () {
		
		for ( let i = 0; i < this.subImapPool.length; i ++ ) {
			const n = this.subImapPool [ this.aliasImapPoint ]
			if ( ++ this.aliasImapPoint === this.subImapPool.length )
				this.aliasImapPoint = 0
			if ( !n.execImap.saveBusy && n.execImap.ready )
				return n.execImap
			
		}
		return null
	}

	private findMainImap ( CallBack ) {
		
		for ( let i = 0; i < this.mainImapPool.length; i ++ ) {
			const n = this.mainImapPool [ this.mainImapPoint ]
			if ( ++ this.mainImapPoint === this.mainImapPool.length )
				this.mainImapPoint = 0
			if ( ! n.execImap.saveBusy && n.execImap.ready ) {
				
				return CallBack ( n.execImap )
			}

		}

		for ( let i = 0; i < this.subImapPool.length; i ++ ) {
			const n = this.subImapPool [ this.aliasImapPoint ]
			if ( ++ this.aliasImapPoint === this.subImapPool.length )
				this.aliasImapPoint = 0
			if ( ! n.execImap.saveBusy && n.execImap.ready ) {
				
				return CallBack ( n.execImap )
			}
				
		}
		return CallBack ()
	}

	private saveMainData = ( n: execImap.execImap ) => {

		if ( !this.maindataPool.length )
			return

		const data = this.maindataPool
		this.maindataPool = []
		console.log ('')
		console.log ('save main Data data.length = ', data.length )
		data.forEach ( nn => {
			const uu = Compress.openPacket ( nn )
			console.log (`[${ uu.uuid }] ==> ${ uu.buffer.length }`)
		})
		console.log ('')
        Async.waterfall ([
            next => Compress.encrypt ( new Buffer (JSON.stringify( data ), 'utf8'), this.password, next ),
            ( _data, next ) => n.sendMessage ( _data, next )
        ], err => {
			if ( err ) {
				this.maindataPool = data.concat ( this.maindataPool )
				return this.pushMainData ( null )
			}
			return this.saveMainData ( n )
		})
	}
	
	private packageData ( data: ISaveDataPool ) {
        if ( data.noExcrypt ) {
            return Compress.encrypt ( data.buffer, this.password, ( err, _data ) => {
                return Compress.packetBuffer ( 0, data.index, data.uuid, _data )
            })
        }
		return Compress.packetBuffer ( 5, data.index, data.uuid, data.buffer )
		
	}


	constructor ( private imapUsers: IinputData[], private password: string, private isServer: boolean, private _newMail: ( data ) => void, private doFock: boolean, private endCall ) {
		
		Async.each ( imapUsers, ( n, down ) => {

			this.masterImap.push ( new imapConnect ( n, ( data ) => { this._newMail ( data )}, 
			this.subImapPool, () => { 
				console.log ( 'ImapCluster =====================> reConnect main ()!' )
				this.pushMainData ( null )
			}, () => {
				console.log ( 'ImapCluster =====================> reConnect sub ()!' )
				this.pushData ( null )
			},
			this.mainImapPool, password,
			isServer, this.CallBackPool, doFock, () => {
				if ( !this.endcall ) {
					this.endcall = true
					return this.endCall ()
				}
				
			}))
			down()
		})
		
	}

	public pushMainData ( buffer: Buffer ) {

		if ( buffer && buffer.length ) {
			const data = Compress.openPacket ( buffer )
			console.log ( `===> uuid[${ data.uuid }],serial[${ data.serial }], buffer[${ data.buffer.length }]` )
			this.maindataPool.unshift ( buffer )
		}
			
		this.findMainImap (( n: execImap.execImap ) => {
			if ( !n ) {
				return console.log ('have no n to save mainData', this.maindataPool.length, this.mainImapPool.length)
			}
			this.saveMainData ( n )
		})

	}

	private saveData ( n: execImap.execImap ) {

		if ( !n )
			return console.log ( 'saveData have not n: execImap.execImap', this.dataPool.length, this.subImapPool.length )

		if ( ! this.dataPool.length )
			return

		const data = this.dataPool
		this.dataPool = []
		const _data = []
		console.log ('')
		console.log ('saveData data.length = ', data.length )
		
		data.forEach ( nn => {
			console.log (`[${ nn.uuid }]==>${ nn.buffer.length }`)
			_data.push ( this.packageData ( nn ))
		})
		console.log ('')
        Async.waterfall ([
            next => Compress.encrypt ( new Buffer (JSON.stringify( data ), 'utf8'), this.password, next ),
			( _Data, next ) => {
				n.sendMessage ( _Data, err => {
					if ( err ) {
						console.log ( 'n.sendMessage save ERROR', ``,err.message )
						this.dataPool = data.concat ( this.dataPool )
						return this.pushData ( null )
					}
					return this.saveData ( n )
				})
			}])
		
		}


	public pushData ( data: ISaveDataPool ) {

		if ( data )
			this.dataPool.unshift ( data )

		return this.saveData ( this.findIdleImap ())
		
	}

	public getDataUuid ( uuid ) {
		const index = this.dataPool.findIndex ( n => {
			return n.uuid === uuid
		})
		if ( index < 0 )
			return null
		const data = this.dataPool[index]
		this.dataPool.splice ( index, 1 )
		return data
	}

	public destroyAllDataUuid ( uuid ) {
		const index = this.dataPool.findIndex ( n => {
			return n.uuid === uuid
		})
		if ( index < 0 )
			return
		//console.log ( '-----------------destroyAllDataUuid: ', uuid )
		this.dataPool.splice ( index, 1 )
		this.destroyAllDataUuid ( uuid )
	}
}
