<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication 
xmlns:mx="http://www.adobe.com/2006/mxml" 
layout="absolute"
creationComplete="creationCompleteHandler()" 
alwaysInFront="{keepInFront_chk.selected}"
viewSourceURL="srcview/index.html">

    <!--

    Copyright (c) 2008 Hans Van de Velde

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.

    -->

    <mx:Style source="css/screen.css" />

    <mx:VBox width="100%" height="100%" styleName="container" id="container">
        <mx:HBox width="100%">
            <mx:Label text="XPanelOnAir (version 0.2)" styleName="title" />
            <mx:Spacer width="100%" />
            <mx:CheckBox label="Keep in front" id="keepInFront_chk" change="keepInFrontChangeHandler(event)" />
        </mx:HBox>
        <mx:Accordion width="100%" height="100%" id="tabNav" change="{tabNavChangeHandler()}">
            <mx:VBox label="Log to panel" paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5" width="100%" height="100%">
            
                <mx:HBox horizontalAlign="right" width="100%">
                    <mx:HBox horizontalGap="1">
                        <mx:Button toggle="true" id="allButton" label="ALL" click="{optionClickHandler(event)}" />
                        <mx:Button toggle="true" id="debugButton" toolTip="DEBUG" click="{optionClickHandler(event)}" styleName="debugButton" />
                        <mx:Button toggle="true" id="infoButton" toolTip="INFO" click="{optionClickHandler(event)}" styleName="infoButton" />
                        <mx:Button toggle="true" id="warnButton" toolTip="WARN" click="{optionClickHandler(event)}" styleName="warnButton" />
                        <mx:Button toggle="true" id="errorButton" toolTip="ERROR / FATAL" click="{optionClickHandler(event)}" styleName="errorButton" />
                    </mx:HBox>
                    <mx:HBox horizontalGap="1">
                        <mx:Label text="Max. lines:" styleName="myLabel" />
                        <mx:NumericStepper id="nbLines" minimum="0" maximum="10000" change="{nbLinesChangeHandler()}" />
                    </mx:HBox>
                    <mx:HBox verticalAlign="middle" paddingLeft="10" horizontalGap="1">
                        <mx:Label text="Word filter:" />
                        <mx:TextInput id="wordFilter_ti" textInput="{wordFilterChange()}" change="{wordFilterChange()}" focusOut="{wordFilterChange()}" toolTip="Filter the list of log messages..." />
                    </mx:HBox>
                    <mx:Spacer width="100%" />
                    <mx:Button label="Export to CSV" click="{exportData()}" />
                    <mx:Button label="Clear" click="{clearGrid()}" />
                </mx:HBox>
                <mx:DataGrid id="dg" dataProvider="{ac}" width="100%" height="100%" doubleClickEnabled="true" doubleClick="{datagridDoubleClickHandler()}">
                    <mx:columns>
                        <mx:DataGridColumn dataField="index" headerText="#" minWidth="5" width="5">
                            <mx:itemRenderer>
                                <mx:Component>
                                    <mx:Label color="{outerDocument.getLabelColor(data.level)}" />
                                </mx:Component>
                            </mx:itemRenderer>
                        </mx:DataGridColumn>
                        <mx:DataGridColumn dataField="time" headerText="Time" minWidth="10" width="10">
                            <mx:itemRenderer>
                                <mx:Component>
                                    <mx:Label color="{outerDocument.getLabelColor(data.level)}" />
                                </mx:Component>
                            </mx:itemRenderer>
                        </mx:DataGridColumn>
                        <mx:DataGridColumn dataField="flashtime" headerText="Flashtime" minWidth="10" width="10">
                            <mx:itemRenderer>
                                <mx:Component>
                                    <mx:Label color="{outerDocument.getLabelColor(data.level)}" />
                                </mx:Component>
                            </mx:itemRenderer>
                        </mx:DataGridColumn>
                        <mx:DataGridColumn dataField="typeLabel" headerText="Type" minWidth="10" width="10">
                            <mx:itemRenderer>
                                <mx:Component>
                                    <mx:Label color="{outerDocument.getLabelColor(data.level)}" />
                                </mx:Component>
                            </mx:itemRenderer>
                        </mx:DataGridColumn>
                        <mx:DataGridColumn dataField="message" headerText="Message" minWidth="75" width="65">
                            <mx:itemRenderer>
                                <mx:Component>
                                    <mx:Label color="{outerDocument.getLabelColor(data.level)}" />
                                </mx:Component>
                            </mx:itemRenderer>
                        </mx:DataGridColumn>
                    </mx:columns>
                </mx:DataGrid>
            </mx:VBox>
            <mx:VBox label="Log to file" paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5" width="100%" height="100%">
                <mx:Text width="100%">
                    <mx:htmlText><![CDATA[Please select or create a text file in which the logs can be written to...]]></mx:htmlText>
                </mx:Text>
                <mx:HBox width="100%">
                    <mx:TextInput id="selectedFilePathTxt" editable="false" />
                    <mx:Button label="Select file" click="{selectLogFile()}" />
                </mx:HBox>
                <mx:TextArea width="100%" height="100%" id="ta" borderThickness="0" backgroundAlpha="0" focusEnabled="false" />
            </mx:VBox>
        </mx:Accordion>
    </mx:VBox>
    
    <mx:Script>
        <![CDATA[
            import mx.core.IFlexDisplayObject;
            import popup.LogMessageDetail;
            import mx.managers.PopUpManager;
            import mx.controls.Text;
            import com.abdulqabiz.utils.DataGridDataExporter;
            import mx.controls.Alert;
            import mx.controls.ToggleButtonBar;
            import mx.events.ItemClickEvent;
            import mx.formatters.DateFormatter;
            import flash.utils.getTimer;
            import mx.collections.ArrayCollection;
            
            [Bindable] private var ac:ArrayCollection;
            private var tabNavIndex:int = 0;

            public const ALL_LEVEL:int = 15;
            private var created:Boolean = false;
            private var lc:LocalConnection;
            private var df:DateFormatter;
            private var filterLevel:Number = 0;
            private var counter:Number = 0;
            private var so:SharedObject;
            private var logFile:File;
            private var textLogFile:File;
            private var stream:FileStream;
            private var toggleButtonArray:Array;
            private var filterWord:String = "";
            private var hasFilterWord:Boolean = false;

            /**
             * Called by the logging target class via local connection
             */            
            public function dispatchMessage(timeDiff:Number, msg:String, level:Number):void
            {
                if(!created) return;
                counter++;
                
                if(tabNavIndex == 0)
                {
                    // Logging to panel
                    // (top down)
                    ac.addItem({index:counter, flashtime:timeDiff, time:df.format(new Date()), message:msg, level:level, typeLabel:getTypeNameForLevel(level)});
                    updateLogLength();
                    dg.scrollToIndex((dg.dataProvider as ArrayCollection).length);
                }
                else
                {
                    // logging to file
                    var newline:String = "#"+counter+", flashtime:"+timeDiff+", time:"+df.format(new Date())+", message:"+msg+", level:"+level+", type:"+getTypeNameForLevel(level)+File.lineEnding;
                    appendLineToTextLog(newline);
                }
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Label color function
             */
            public function getLabelColor(level:Number):Number
            {
                switch(level)
                {
                    case 8:
                        return 0xff0000;
                    default:
                        return 0x000000;
                }
            }

            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Empty the grid data
             */
            public function clearGrid():void
            {
                ac.removeAll();
                ac.refresh();
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            public function clearText():void
            {
                txt = "";
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Get label for level
             */
            protected function getTypeNameForLevel(level:int):String
            {
                var typeLabel:String = "";
                switch(level)
                {
                    case 1:
                        typeLabel = "DEBUG";
                        break;
                    case 2:
                        typeLabel = "INFO";
                        break;
                    case 4:
                        typeLabel = "WARN";
                        break;
                    case 8:
                        typeLabel = "ERROR";
                        break;
                    default:
                }
                return typeLabel;
            }

            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Everything's on stage is rendered and ready to use
             */
            private function creationCompleteHandler():void
            {
                toggleButtonArray = [allButton, debugButton, infoButton, warnButton, errorButton];
                
                ac = new ArrayCollection();
                ac.filterFunction = filterLogEntries;
                
                lc = new LocalConnection();
                lc.allowDomain('*');
                lc.connect("_xpanel1");
                lc.client = this;
                df = new DateFormatter();
                df.formatString = "HH:NN:SS";
                created = true;
                
                //
                so = SharedObject.getLocal("xpanelonair", "/");
                if(so.data.hasOwnProperty("keepInFront"))
                {
                    keepInFront_chk.selected = so.data.keepInFront;
                }
                else
                {
                    keepInFront_chk.selected = true;
                }
                
                if(so.data.hasOwnProperty("filterLevel"))
                {
                    setFilterLevel(so.data.filterLevel);
                }
                else
                {
                    setFilterLevel(ALL_LEVEL);
                }
                
                if(so.data.hasOwnProperty("nbLines"))
                {
                    nbLines.value = parseInt(""+so.data.nbLines);
                }
                else
                {
                    so.data.nbLines = nbLines.value = 100;
                }
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * 
             */
            private function datagridDoubleClickHandler():void
            {
                var popup:IFlexDisplayObject =  PopUpManager.createPopUp(this, LogMessageDetail, true);
                (popup as LogMessageDetail).setItem(dg.selectedItem); 
                PopUpManager.centerPopUp(popup);
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * 
             */
            protected function setFilterLevel(value:int):void
            {
                filterLevel = value;
                
                var addUp:int = 1;
                for(var j:uint = 1; j<toggleButtonArray.length; j++)
                {
                    var btn:Button = toggleButtonArray[j] as Button;
                    btn.selected = ((addUp & filterLevel) == addUp) ? true : false;
                    addUp = addUp*2;
                }
                
                if(filterLevel == ALL_LEVEL) allButton.selected = true;
                
                ac.refresh();
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Data collection filter
             */
            private function filterLogEntries(item:Object):Boolean 
            {
                 if ((item.level & filterLevel) == item.level)   // using bitwise AND operator to check if log message complies with current filterLevel
                {
                    if(hasFilterWord)
                    {
                        if((item.message as String).indexOf(filterWord) != -1) return true;
                        else return false;
                    }
                    else return true;
                } 
                else 
                {
                    return false;
                }
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * BUTTON STATES calculated based on bitwise operators
             * For more information, check out http://www.moock.org/asdg/technotes/bitwise/
             */
            protected function optionClickHandler(event:Event):void
            {
                if(event.target == allButton)
                {
                    filterLevel = (allButton.selected) ? ALL_LEVEL : 0;
                    for(var i:uint = 1; i<toggleButtonArray.length; i++) (toggleButtonArray[i] as Button).selected = allButton.selected;
                }
                else
                {
                    allButton.selected = false;
                    
                    filterLevel = 0;
                    var addUp:int = 1;
                    for(var j:uint = 1; j<toggleButtonArray.length; j++)
                    {
                        if((toggleButtonArray[j] as Button).selected) filterLevel += addUp;
                        addUp = addUp*2;
                    }
                }
                
                so.data.filterLevel = filterLevel;
                
                //
                ac.refresh();
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function wordFilterChange():void
            {
                filterWord = wordFilter_ti.text;
                if(filterWord.length > 0) hasFilterWord = true;
                else hasFilterWord = false;
                
                //
                ac.refresh();
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function tabNavChangeHandler():void
            {
                ac = new ArrayCollection();
                tabNavIndex = tabNav.selectedIndex;
                
                if(tabNavIndex == 0)
                {
                    closeTextLogFile();
                }
                else
                {
                    openTextLogFile();
                }
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function keepInFrontChangeHandler(event:Event):void
            {
                so.data.keepInFront = (event.target as CheckBox).selected
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function nbLinesChangeHandler():void
            {
                so.data.nbLines = nbLines.value;
                updateLogLength();
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function saveFile():void 
            {
                if (logFile) {
                    if (stream != null)    
                    {
                        stream.close();
                    }
                    stream = new FileStream();
                    stream.openAsync(logFile, FileMode.WRITE);
                    stream.addEventListener(IOErrorEvent.IO_ERROR, writeIOErrorHandler);
                    var str:String = DataGridDataExporter.exportCSV(dg);
                    stream.writeUTFBytes(str);
                    stream.close();
                } 
                else
                {
                    exportData();
                }
            }
            
            //-------------------------------------------------------------------------------------------------------

            private function openTextLogFile():void
            {
                if(textLogFile)
                {
                    stream = new FileStream();
                    stream.openAsync(textLogFile, FileMode.APPEND);
                    selectedFilePathTxt.text = textLogFile.nativePath;
                    ta.text = "File opened succesfully.";
                }
            }
            
            //-------------------------------------------------------------------------------------------------------

            private function closeTextLogFile():void
            {
                if(textLogFile && stream)
                {
                    stream.close();
                }
            }
            
            //-------------------------------------------------------------------------------------------------------

            /**
            * Called when the user clicks the "Export" button. Opens a Save As dialog box, in which the 
            * user selects a logFile path.
            */
            private function exportData():void 
            {
                logFile = File.desktopDirectory.resolvePath("log.csv");
                var fileChooser:File;
                fileChooser = logFile;
                fileChooser.browseForSave("Save As");
                fileChooser.addEventListener(Event.SELECT, saveAsFileSelected);
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function selectLogFile():void 
            {
                textLogFile = File.desktopDirectory.resolvePath("log.txt");
                var fileChooser:File;
                fileChooser = textLogFile;
                fileChooser.browseForSave("Select log file");
                fileChooser.addEventListener(Event.SELECT, saveTextLogFileSelected);
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Adjust the length of the collection of log entries to be displayed in the datagrid
             */
            private function updateLogLength():void
            {
                while(ac.length > nbLines.value)
                {
                    ac.removeItemAt(0);
                }
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Adds a line to the log file
             */
            private function appendLineToTextLog(line:String):void
            {
                try
                {
                    stream.writeUTFBytes(line);
                    ta.text = "";
                }
                catch(e:Error)
                {
                    ta.text = "(Error!) Could not write to file...";
                }
            }        

            //-------------------------------------------------------------------------------------------------------
            
            /**
            * Called when the user selects the file path in the Save As dialog box. The method passes the selected 
            * logFile to the File object and calls the saveFile() method, which saves the logFile.
            */
            private function saveAsFileSelected(event:Event):void 
            {
                logFile = event.target as File;
                saveFile();
                logFile.removeEventListener(Event.SELECT, saveAsFileSelected);
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            private function saveTextLogFileSelected(event:Event):void 
            {
                textLogFile = event.target as File;
                openTextLogFile();
                textLogFile.removeEventListener(Event.SELECT, saveTextLogFileSelected);
            }
            
            //-------------------------------------------------------------------------------------------------------
            
            /**
             * Handles I/O errors that may come about when writing the logFile.
             */
            private function writeIOErrorHandler(event:Event):void 
            {
                Alert.show("The data could not be exported to file.", "Error", Alert.OK, this);
            }
        ]]>
    </mx:Script>
</mx:WindowedApplication>