package controllers
{
    import events.VoteEvent;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    
    import models.VoteDefinition;
    
    import netgroup.NetGroupEvent;
    import netgroup.NetGroupManager;
    
    
    /** 
     * Event sent once the netConnection and netGroup connections have been 
     * successful, and the group is ready to be used. 
     */
    [Event("connectedToGroup", type="netgroup.NetGroupEvent")]
    
    
    /** 
     * Event sent when the role has been set, by the user of from the netGroup
     */
    [Event("roleSet", type="events.VoteEvent")]
    
    
    [Event("startVote", type="events.VoteEvent")]
    
    [Event("stopVote", type="events.VoteEvent")]
    
    [Event("showResults", type="events.VoteEvent")]
    
    /** answersReceived is sent when receiving an answer which isn't the last one */
    [Event("answersReceived", type="events.VoteEvent")]
    
    
    /**
     * VoteController manage the NetGroup connection and all data exchanges.
     * VoteController is implemented as a Singleton.
     * 
     */
    public class VoteController extends EventDispatcher
    {
        
        /* Singleton management */
        static private var _instance:VoteController;
        static public function get instance():VoteController
        {
            if (!_instance) _instance = new VoteController();
            return _instance;
        }
        
        
        
        [Bindable]public var connected:Boolean = false;
        
        /* Role definition */
        static public const ROLE_CLIENT:String  = 'client'; 
        static public const ROLE_MANAGER:String = 'manager'; 
        private var _role:String;
        [Bindable]public function get role():String { return _role; }
        public function set role(value:String):void
        {
            if (_role != null) return; // Can be set only once
            _role = value;
            _handshakeVoteManager(); 
            dispatchEvent(new VoteEvent(VoteEvent.ROLE_SET));
        }

 
        
        static public const SUBJECT_SET_VOTE_MANAGER_EXISTENCE:String = 'setVoteManagerExistence';
        static public const SUBJECT_START_VOTE:String       = 'startVote';
        static public const SUBJECT_STOP_VOTE:String        = 'stopVote';
        static public const SUBJECT_SUBMIT_ANSWERS:String   = 'submitAnswers';
        static public const SUBJECT_SHOW_RESULTS:String     = 'showResults';
                
        protected var netGroupMgr:NetGroupManager;
        
        /* Current vote buffers */
        public var currentVoteDefinition:VoteDefinition;
        public var currentVoteAnswers:Array;
        public var currentVoteUsers:int;
        public var totalVoteClients:int;
        
        public function VoteController(target:IEventDispatcher=null)
        {
            super(target);
            if (_instance) return;

            netGroupMgr = new NetGroupManager();
            netGroupMgr.applicationName = "active.tutsplus.exemples/";
            netGroupMgr.groupName       = "sampleApp";
            netGroupMgr.addEventListener(NetGroupEvent.POSTING, onNetGroupMessage, false,0,true);
            netGroupMgr.addEventListener(NetGroupEvent.CONNECTED_TO_GROUP, onConnectionEstablished, false,0,true);
            netGroupMgr.addEventListener(NetGroupEvent.DISCONNECTED_FROM_GROUP, onConnectionLost, false,0,true);
            netGroupMgr.addEventListener(NetGroupEvent.NEIGHBOR_JOINED, onNeighborJoined, false,0,true);
            
            netGroupMgr.connect();
        }
        
        
        protected function onConnectionEstablished(event:NetGroupEvent):void
        {
            connected = true;
            dispatchEvent(new Event(NetGroupEvent.CONNECTED_TO_GROUP));
        }
        
        
        protected function onConnectionLost(event:NetGroupEvent):void
        {
            connected = false;
        }
        
        
        protected function onNeighborJoined(event:NetGroupEvent):void
        {
            _handshakeVoteManager();
        }
        
        private function _handshakeVoteManager():void
        {
            if (role == ROLE_MANAGER)
            {  // If I'm a vote manager, I warn this newcomer of my existence
                netGroupMgr.sendMessage(SUBJECT_SET_VOTE_MANAGER_EXISTENCE, {});
            }
        }
        
        
        /** 
         * doStartVote ill be called by submitVote for the VoteManager, and from 
         * onNetGroupMessage for the voteClients, when receiving a SUBJECT_START_VOTE message
         */
        protected function doStartVote(def:VoteDefinition):void
        {
            var evt:VoteEvent = new VoteEvent(VoteEvent.START_VOTE);
            evt.data = def;
            dispatchEvent(evt);
        }
        
        
        protected function registerClientAnswers(answers:Array):void
        {
            // answers contains the position and the answers selected by the user, increment our counters
            for each (var ind:int in answers)
            {
                currentVoteAnswers[ind]++;
            }
            
            currentVoteUsers++;
            
            // notify the manager of the vote progression
            dispatchEvent(new VoteEvent(VoteEvent.ANSWERS_RECEIVED));
            
            
            if (currentVoteUsers >= totalVoteClients)
            {
                // We have received all the answers
                dispatchEvent(new VoteEvent(VoteEvent.STOP_VOTE));
                netGroupMgr.sendMessage(SUBJECT_STOP_VOTE, 'all answers received');
            } 
            
        }
        
        public function parseResultsForBroadcast():void
        {
            var msg:Object = {
                answers:currentVoteAnswers, 
                voteDefinition:currentVoteDefinition,
                voteUserCnt:currentVoteUsers, 
                totalVoteClients:totalVoteClients
            };
            
            netGroupMgr.sendMessage(SUBJECT_SHOW_RESULTS, msg);
            
        }
        
        
        public function submitVote(def:VoteDefinition):void
        {
            if (role != ROLE_MANAGER) return;
            
            currentVoteDefinition = def;
            currentVoteAnswers = [null,0,0,0,0,0]; // no answers logged yet. 0 is the question and therefore cannot be answered
            currentVoteUsers = 0;
            totalVoteClients = netGroupMgr.neighborCount; // We store the number of answer we'll be waiting.
            
            doStartVote(def);
            netGroupMgr.sendMessage(SUBJECT_START_VOTE, def);
            
        }
        
        
        public function submitAnswers(answers:Array /*of int*/):void
        {
            netGroupMgr.sendMessage(SUBJECT_SUBMIT_ANSWERS, answers);

        }
        
        
        
        /**
         * Callback for all the netGroup message we will receive. 
         * Message routing, based on the message subject, will be done here.  
         */
        protected function onNetGroupMessage(event:NetGroupEvent):void
        {
            var message:Object = event.message;
            if (!message) return;
            var subject:String = event.subject as String;
          
            switch (subject) 
            {
                case VoteController.SUBJECT_SET_VOTE_MANAGER_EXISTENCE:
                    // Another peer is already a vote manager, so I'll be a 
                    // vote client if I haven't yet set my role
                    if (role == null)
                        role = ROLE_CLIENT;
                    break;
                
                case SUBJECT_START_VOTE:
                    // We just received a vote, send it !
                    doStartVote(new VoteDefinition(message));
                    break;
                
                case SUBJECT_SUBMIT_ANSWERS:
                    if (role == ROLE_MANAGER)
                    {
                        registerClientAnswers(message as Array);
                    }
                    break;
                    
                case SUBJECT_STOP_VOTE:
                    dispatchEvent(new VoteEvent(VoteEvent.STOP_VOTE));
                    break;
                    
                case SUBJECT_SHOW_RESULTS:
                    currentVoteDefinition = new VoteDefinition(message.voteDefinition);
                    currentVoteAnswers    = message.answers;
                    currentVoteUsers = message.voteUserCnt;
                    totalVoteClients = message.totalVoteClients;
                    
                    dispatchEvent(new VoteEvent(VoteEvent.SHOW_RESULTS));
                    
                    break;
            }
                
        }
        
    }
}