var CloudCmd, Util, DOM, CloudFunc;

(function(Util, window) {
    'use strict';
    
    /* global rendy */
    
    var DOMFunc                     = function() {},
        DOMProto,
        
        ImagesProto                 = function() {
            var Images,
                ImageElementProto   = function() {
                    var LOADING         = 'loading',
                        LoadingImage,
                        HIDDEN          = 'hidden',
                        ERROR           = 'error';
                    
                    function init() {
                        var is;
                        
                        if (!LoadingImage) {
                            LoadingImage    = LOADING;
                            is              = DOM.isSVG();
                            
                            if (is)
                                LoadingImage += '-svg';
                            else
                                LoadingImage += '-gif';
                        }
                    }
                    
                    function getElement() {
                        var element;
                        
                        init();
                        
                        element = DOM.load({
                            name        : 'span',
                            id          : 'js-status-image',
                            className   : 'icon',
                            attribute   : 'data-progress',
                            notAppend   : true
                        });
                        
                        return element;
                    }
                    
                    this.get        = getElement;
                    
                    /* Функция создаёт картинку загрузки */
                    this.loading    = function() {
                        var element     = getElement(),
                            classList   = element.classList;
                        
                        classList.add(LOADING, LoadingImage);
                        classList.remove(ERROR, HIDDEN);
                        
                        return element;
                    };
                    
                    /* Функция создаёт картинку ошибки загрузки */
                    this.error      = function() {
                        var element     = getElement(),
                            classList   = element.classList;
                        
                        classList.add(ERROR);
                        classList.remove(HIDDEN, LOADING, LoadingImage);
                        
                        return element;
                    };
            };
            
            Images          = new ImageElementProto();
            
            this.show       = load;
            this.show.load  = load;
            this.show.error = error;
            
            /**
             * Function shows loading spinner
             * position = {top: true};
             */
            function load(position, panel) {
                var current,
                    image           = Images.loading(),
                    parent          = image.parentElement,
                    refreshButton   = DOM.getRefreshButton(panel);
                
                if (position === 'top') {
                    current         = refreshButton.parentElement;
                } else {
                    current         = DOM.getCurrentFile();
                    
                    if (current)
                        current     = DOM.getByDataName('js-name', current);
                    else
                        current     = refreshButton.parentElement;
                }
                
                if (!parent || (parent && parent !== current))
                    current.appendChild(image);
                
                DOM.show(image);
                
                return image;
            }
                
            function error(text) {
                var image = Images.error();
                
                DOM.show(image);
                image.title = text;
                
                CloudCmd.log(text);
                
                return image;
            }
            
            /**
             * hide load image
             */
            this.hide       = function() {
                var element = Images.get();
                DOM.hide(element);
                
                return this;
            };
            
            this.setProgress    = function(value, title) {
                var DATA    = 'data-progress',
                    element = Images.get();
                    
                if (element) {
                    element.setAttribute(DATA, value + '%');
                    
                    if (title)
                        element.title = title;
                }
                
                return this;
            };
                
            this.clearProgress  = function() {
                var DATA    = 'data-progress',
                    element = Images.get();
                    
                 if (element) {
                    element.setAttribute(DATA, '');
                    element.title = '';
                }
                
                return this;
            };
        },
        
        DOMTreeProto                = function() {
            var DOM     = this;
            
            /**
             * check class of element
             *
             * @param element
             * @param pClass
             */
            this.isContainClass          = function(element, className) {
                var ret, classList;
                
                if (!element)
                    throw Error('element could not be empty!');
                
                if (!className)
                    throw Error('className could not be empty!');
                
                classList   = element.classList;
                ret         = classList.contains(className);
                    
                return ret;
            };
            
            /**
             * Function search element by tag
             * @param pTag - className
             * @param element - element
             */
            this.getByTag                = function(pTag, element) {
                return (element || document).getElementsByTagName(pTag);
            };
            
            /**
             * Function search element by id
             * @param Id - id
             */
            this.getById                = function(id, element) {
                return (element || document).querySelector('#' + id);
            };
            
            /**
             * Function search first element by class name
             * @param pClass - className
             * @param element - element
             */
            this.getByClass             = function(pClass, elementParam) {
                var element = elementParam || document,
                    ret     = this.getByClassAll(pClass, element)[0];
                
                return ret;
            };
            
            this.getByDataName          = function(attribute, element) {
                var ret,
                    selector    = '[' + 'data-name="' + attribute + '"]';
                
                if (!element)
                    element     = document;
                
                ret             = element.querySelector(selector);
                
                return ret;
            };
            
            /**
             * Function search element by class name
             * @param pClass - className
             * @param element - element
             */
            this.getByClassAll          = function(pClass, element) {
                return (element || document).getElementsByClassName(pClass);
            };
            
            /**
             * check SVG SMIL animation support
             */
            this.isSVG                  = function() {
                var ret, svgNode, name,
                    create  = document.createElementNS,
                    SVG_URL = 'http://www.w3.org/2000/svg';
                
                if (create) {
                    create  = create.bind(document);
                    svgNode = create(SVG_URL, 'animate');
                    name    = svgNode.toString();
                    ret     = /SVGAnimate/.test(name);
                }
                
                return ret;
            };
            
            /**
             * add class=hidden to element
             *
             * @param element
             */
            this.hide                    = function(element) {
                element.classList.add('hidden');
                return DOM;
            };
            
            this.show                    = function(element) {
                element.classList.remove('hidden');
                return DOM;
            };
        },
        
        CmdProto                    = function() {
            var Cmd                     = this,
                CurrentInfo             = {},
                CURRENT_FILE            = 'current-file',
                SELECTED_FILE           = 'selected-file',
                SelectType              = '*.*',
                TITLE                   = 'Cloud Commander',
                Title,
                TabPanel   = {
                    'js-left'        : null,
                    'js-right'       : null
                };
            
            /**
             * private function thet unset currentfile
             *
             * @currentFile
             */
            function unsetCurrentFile(currentFile) {
                var ret = DOM.isCurrentFile(currentFile);
                
                if (ret)
                    currentFile.classList.remove(CURRENT_FILE);
                
                return ret;
            }
            
            this.loadRemote         = function(name, options, callback) {
                var o           = options,
                    Files       = DOM.Files;
                
                if (!callback)
                    callback = options;
                
                if (o.name && window[o.name])
                    callback();
                else
                    Files.get(['config', 'modules'], function(error, config, modules) {
                        var remoteTmpls, local, remote,
                            load        = DOM.load,
                            prefix      = CloudCmd.PREFIX,
                            online      = config.online && navigator.onLine,
                            
                            remoteObj   = Util.findObjByNameInArr(modules, 'remote'),
                            module      = Util.findObjByNameInArr(remoteObj, name),
                            
                            isArray     = Util.type.array(module.local),
                            version     = module.version,
                            
                            funcON      = function() {
                                load.parallel(remote, function(error) {
                                    if (error)
                                        funcOFF();
                                    else
                                        callback();
                                });
                            },
                            
                            funcOFF     = function() {
                                load.parallel(local, callback);
                            };
                            
                        if (isArray) {
                            remoteTmpls = module.remote;
                            local       = module.local;
                        } else {
                           remoteTmpls  = [module.remote];
                           local        = [module.local];
                        }
                        
                        local   = local.map(function(url) {
                            return o.noPrefix ? url : prefix + url;
                        });
                        
                        remote  = remoteTmpls.map(function(tmpl) {
                            return rendy(tmpl, {
                                version: version
                            });
                        });
                        
                        Util.exec.if(online, funcON, funcOFF);
                    });
                
                return DOM;
            };
            
            /**
             * load jquery from google cdn or local copy
             * @param callback
             */
            this.loadJquery         = function(callback) {
                DOM.loadRemote('jquery', {
                    name    : '$',
                    noPrefix: true
                }, callback);
                
                return DOM;
            };
            
            this.loadSocket         = function(callback) {
                DOM.loadRemote('socket', {
                    name    : 'io',
                    noPrefix: true
                }, callback);
                
                return DOM;
            };
            
            /** function loads css and js of Menu
             * @param callback
             */
            this.loadMenu           = function(callback) {
                return DOM.loadRemote('menu', callback);
            };
            
            this.uploadFiles        = function(dir, files) {
                var array,
                    slice   = [].slice,
                    i       = 0,
                    n       = 0,
                    func    = function(name) {
                        return function() {
                            CloudCmd.refresh(null, function() {
                                var current = DOM.getCurrentByName(name);
                                DOM.setCurrentFile(current);
                            });
                        };
                    },
                    percent = function(i, n, per) {
                        var value;
                        
                        if (!per)
                            per = 100;
                        
                        value = Math.round(i * per / n);
                        
                        return value;
                    },
                    
                    step    = function(n) {
                        return 100 / n;
                    },
                    load    = function(file, callback) {
                        var uploader,
                            Images  = DOM.Images,
                            name    = file.name,
                            path    = dir + name;
                            
                            ++i;
                            
                            uploader = DOM.load.put(path, file);
                            uploader.on('progress', function(count) {
                                var max     = step(n),
                                    value   = (i - 1) * max + percent(count, 100, max);
                                
                                Images.show.load('top');
                                Images.setProgress(Math.round(value));
                            });
                            
                            uploader.on('end', callback);
                    };
                
                if (!files) {
                    files   = dir;
                    dir     = CurrentInfo.dirPath;
                }
                
                n       = files.length;
                array   = slice.call(files);
                
                if (n) {
                    Util.exec.eachSeries(array, load, func(files[0].name));
                }
            };
            
            /**
             * create new folder
             *
             */
            this.promptNewDir        = function() {
                promptNew('directory', '?dir');
            };
            
            /**
             * create new file
             *
             * @typeName
             * @type
             */
            this.promptNewFile       = function() {
                 promptNew('file');
            };
            
            function promptNew(typeName, type) {
                var RESTful = DOM.RESTful,
                    Dialog  = DOM.Dialog,
                    path    = '',
                    name    = Cmd.getCurrentName(),
                    dir     = Cmd.getCurrentDirPath(),
                    msg     = 'New ' + typeName || 'File';
                
                if (name === '..')
                    name = '';
                
                Dialog.prompt(TITLE, msg, name, {cancel: false}).then(function(name) {
                    path        = dir + name;
                    
                    if (type)
                        path    += type;
                        
                    if (name)
                        RESTful.write(path, function(error) {
                            !error && CloudCmd.refresh(null, function() {
                                var current = DOM.getCurrentByName(name);
                                
                                DOM.setCurrentFile(current);
                            });
                        });
                });
            }
            
            /**
             * get current direcotory name
             */
            this.getCurrentDirName           = function() {
                var ret,
                    substr,
                    /* получаем имя каталога в котором находимся */
                    href    = this.getCurrentDirPath();
                
                href    = href.replace(/\/$/, '');
                substr  = href.substr(href, href.lastIndexOf('/'));
                ret     = href.replace(substr + '/', '') || '/';
                
                return ret;
            };
            
            /**
             * get current direcotory path
             */
            this.getCurrentDirPath       = function(panel) {
                var ret, path;
                
                if (!panel)
                    panel   =  this.getPanel();
                
                path        =  this.getByDataName('js-path', panel);
                ret         = path && path.textContent;
                
                return ret;
            };
            
            /**
             * get current direcotory path
             */
            this.getParentDirPath       = function(panel) {
                var path    = DOM.getCurrentDirPath(panel),
                    dirName = DOM.getCurrentDirName() + '/';
                
                if (path !== '/')
                    path = path.replace(dirName, '');
                
                return path;
            };
            
            /**
             * get not current direcotory path
             */
            this.getNotCurrentDirPath       = function() {
                var panel  = this.getPanel({active: false}),
                    path   = this.getCurrentDirPath(panel);
                
                return path;
            };
            
            /**
             * unified way to get current file
             *
             * @currentFile
             */
            this.getCurrentFile             = function() {
                var ret = this.getByClass(CURRENT_FILE);
                
                return ret;
            };
            
            /**
             * get current file by name
             */
            this.getCurrentByName           = function(name, panelParam) {
                var element,
                    panel   = panelParam || CurrentInfo.panel;
                
                name    = 'js-file-' + name;
                element = DOM.getByDataName(name, panel);
                
                return element;
            };
            
            /**
             * unified way to get current file
             *
             * @currentFile
             */
            this.getSelectedFiles         = function() {
                var panel       = DOM.getPanel(),
                    selected    = this.getByClassAll(SELECTED_FILE, panel),
                    ret         = [].slice.call(selected);
                
                return ret;
            };
            
            
            /*
             * unselect all files
             */
            this.unselectFiles          = function(files) {
                var array,
                    isArray = Array.isArray(files);
                
                if (!files) {
                    array = DOM.getSelectedFiles();
                } else if (!isArray)
                    array = [].slice.call(files);
                
                array.forEach(DOM.toggleSelectedFile);
            };
            
            /**
             * get all selected files with current included
             *
             * @currentFile
             */
            this.getActiveFiles         = function() {
                var current     = DOM.getCurrentFile(),
                    files       = DOM.getSelectedFiles(),
                    selected    = ~files.indexOf(current),
                    name        = DOM.getCurrentName(current);
                
                if (!selected && name !== '..')
                    files.push(current);
                
                return files;
            };
            
            /**
             * get size
             * @currentFile
             */
            this.getCurrentSize          = function(currentFile) {
                var current     = currentFile || Cmd.getCurrentFile(),
                    size        = this.getByDataName('js-size', current);
                
                /* если это папка - возвращаем слово dir вместо размера*/
                size    = size
                    .textContent
                    .replace(/^<|>$/g, '');
                
                return size;
            };
            
            /**
             * get size
             * @currentFile
             */
            this.loadCurrentSize          = function(callback, currentFile) {
                var RESTful     = DOM.RESTful,
                    Images      = DOM.Images,
                    current     = currentFile || this.getCurrentFile(),
                    query       = '?size',
                    link        = this.getCurrentPath(current);
                
                Images.show.load();
                
                if (name !== '..')
                    RESTful.read(link + query, function(error, size) {
                        if (!error) {
                            DOM.setCurrentSize(size, current);
                            Util.exec(callback, current);
                            Images.hide();
                        }
                    });
            };
            
            /**
             * load hash
             * @callback
             * @currentFile
             */
            this.loadCurrentHash          = function(callback, currentFile) {
                var RESTful     = DOM.RESTful,
                    current     = currentFile || DOM.getCurrentFile(),
                    query       = '?hash',
                    link        = DOM.getCurrentPath(current);
                
                RESTful.read(link + query, callback);
            };
            
            /**
             * load current modification time of file
             * @callback
             * @currentFile
             */
            this.loadCurrentTime          = function(callback, currentFile) {
                var RESTful     = DOM.RESTful,
                    current     = currentFile || this.getCurrentFile(),
                    query       = '?time',
                    link        = this.getCurrentPath(current);
                
                RESTful.read(link + query, callback);
            };
            
            /**
             * set size
             * @currentFile
             */
            this.setCurrentSize          = function(size, currentFile) {
                var current         = currentFile || this.getCurrentFile(),
                    sizeElement     = this.getByDataName('js-size', current);
                
                sizeElement.textContent = size;
            
            };
            
            /**
             * @currentFile
             */
            this.getCurrentMode          = function(currentFile) {
                var ret,
                    current    = currentFile || this.getCurrentFile(),
                    lMode       = this.getByDataName('js-mode', current);
                    ret        = lMode.textContent;
                
                return ret;
            };
            
            /**
             * @currentFile
             */
            this.getCurrentOwner         = function(currentFile) {
                var ret,
                    current     = currentFile || this.getCurrentFile(),
                    owner       = this.getByDataName('js-owner', current);
                
                ret             = owner.textContent;
                
                return ret;
            };
            
            /**
             * unified way to get current file content
             *
             * @param callback
             * @param currentFile
             */
             this.getCurrentData             = function(callback, currentFile) {
                var hash,
                    RESTful         = DOM.RESTful,
                    Dialog          = DOM.Dialog,
                    Info            = DOM.CurrentInfo,
                    current         = currentFile || DOM.getCurrentFile(),
                    path            = DOM.getCurrentPath(current),
                    isDir           = DOM.isCurrentIsDir(current),
                    
                    func            = function(error, data) {
                        var length,
                           ONE_MEGABYTE    = 1024 * 1024 * 1024;
                        
                        if (!error) {
                            if (Util.type.object(data))
                                data = Util.json.stringify(data);
                            
                            length  = data.length;
                            
                            if (hash && length < ONE_MEGABYTE)
                                DOM.saveDataToStorage(path, data, hash);
                        }
                        
                        callback(error, data);
                    };
                
                if (Info.name === '..') {
                    Dialog.alert.noFiles(TITLE);
                    callback(Error('No files selected!'));
                } else if (isDir) {
                    RESTful.read(path, func);
                } else {
                    DOM.checkStorageHash(path, function(error, equal, hashNew) {
                        if (error) {
                            callback(error);
                        } else if (equal) {
                            DOM.getDataFromStorage(path, callback);
                        } else {
                            hash = hashNew;
                            RESTful.read(path, func);
                        }
                    });
                }
            };
            
            
            /**
             * unified way to save current file content
             *
             * @callback - function({data, name}) {}
             * @currentFile
             */
            this.saveCurrentData             = function(url, data, callback, query) {
                if (!query)
                    query = '';
                
                DOM.RESTful.write(url + query, data, function(error) {
                    !error && DOM.saveDataToStorage(url, data);
                });
            };
            
            /**
             * unified way to get RefreshButton
             */
            this.getRefreshButton        = function(panel) {
                var currentPanel    = panel || this.getPanel(),
                    refresh         = this.getByDataName('js-refresh', currentPanel);
                
                return refresh;
            };
            
            
            /**
             * unified way to set current file
             */
            this.setCurrentFile          = function(currentFile, options) {
                var ret, path, pathWas, title,
                    o               = options,
                    FS              = CloudFunc.FS,
                    CENTER          = true,
                    currentFileWas  = this.getCurrentFile();
                
                if (currentFile) {
                    if (currentFileWas) {
                        pathWas     = DOM.getCurrentDirPath();
                        unsetCurrentFile(currentFileWas);
                    }
                    
                    currentFile.classList.add(CURRENT_FILE);
                    
                    path        = DOM.getCurrentDirPath();
                    
                    if (path !== pathWas) {
                        title   = CloudFunc.getTitle(path);
                        this.setTitle(title);
                        
                        /* history could be present
                         * but it should be false
                         * to prevent default behavior
                         */
                        if (!o || o.history !== false) {
                            if (path !== '/')
                                path    = FS + path;
                            
                            DOM.setHistory(path, null, path);
                        }
                    }
                    
                    /* scrolling to current file */
                    this.scrollIntoViewIfNeeded(currentFile, CENTER);
                    
                    ret = true;
                    
                    Cmd.updateCurrentInfo();
                }
                
                return  this;
            };
            
             /*
              * set current file by position
              *
              * @param layer    - element
              * @param          - position {x, y}
              */
            this.getCurrentByPosition               = function(position) {
                var element, tag, isChild,
                    x   = position.x,
                    y   = position.y;
                
                element = document.elementFromPoint(x, y),
                tag     = element.tagName;
                
                isChild = /A|SPAN|LI/.test(tag);
                
                if (!isChild) {
                    element = null;
                } else {
                    switch (tag) {
                    case 'A':
                        element = element.parentElement.parentElement;
                        break;
                    
                    case 'SPAN':
                        element = element.parentElement;
                        break;
                    }
                }
                
                if (element && element.tagName !== 'LI')
                    element = null;
                
                return element;
            };
            
            /**
             * select current file
             * @param currentFile
             */
            this.toggleSelectedFile              = function(currentFile) {
                var current     = currentFile || this.getCurrentFile();
                    
                current.classList.toggle(SELECTED_FILE);
                
                return Cmd;
            };
            
            this.toggleAllSelectedFiles         = function() {
                var i, n,
                    panel       = DOM.getPanel(),
                    files       = DOM.getFiles(panel),
                    name        = DOM.getCurrentName(files[0]);
                
                if (name === '..')
                    i = 1;
                else
                    i = 0;
                
                n = files.length;
                
                for (; i < n; i++)
                    DOM.toggleSelectedFile(files[i]);
                
                return Cmd;
            };
            
            function selectByPattern(msg, files) {
                var n, regExp,
                    Dialog      = DOM.Dialog,
                    allMsg      = 'Specify file type for ' + msg + ' selection',
                    i           = 0,
                    matches     = 0,
                    name,
                    shouldSel   = msg === 'expand',
                    isSelected, isMatch,
                    current;
                
                Dialog.prompt(TITLE, allMsg, SelectType, {cancel: false}).then(function(type) {
                    SelectType  = type;
                    
                    regExp      = Util.getRegExp(type);
                    
                    n           = files && files.length;
                    for (i = 0; i < n; i++) {
                        current = files[i];
                        name    = DOM.getCurrentName(current);
                            
                        if (name !== '..') {
                            isMatch     = regExp.test(name);
                            
                            if (isMatch) {
                                ++matches;
                                
                                isSelected  = DOM.isSelected(current);
                                
                                if (shouldSel)
                                    isSelected = !isSelected;
                                
                                if (isSelected)
                                    DOM.toggleSelectedFile(current);
                            }
                        }
                    }
                    
                    if (!matches)
                        Dialog.alert('Select Files', 'No matches found!');
                });
            }
            
            /**
             * open dialog with expand selection
             */
            this.expandSelection                = function() {
                var msg     = 'expand',
                    files   = CurrentInfo.files;
                
                selectByPattern(msg, files);
            };
            
            /**
             * open dialog with shrink selection
             */
            this.shrinkSelection                = function() {
                var msg     = 'shrink',
                    files   = DOM.getSelectedFiles();
               
               selectByPattern(msg, files);
            };
            
            /**
             * setting history wrapper
             */
            this.setHistory              = function(data, title, url) {
                var ret = window.history;
                
                url = CloudCmd.PREFIX + url;
                
                if (ret)
                    history.pushState(data, title, url);
                
                return ret;
            };
            
            /**
             * set title or create title element
             *
             * @param name
             */
            
            this.setTitle                = function(name) {
                if (!Title)
                    Title = DOM.getByTag('title')[0] ||
                            DOM.load({
                                name            : 'title',
                                innerHTML       : name,
                                parentElement   : document.head
                            });
                
                Title.textContent = name;
                
                return this;
            };
            
            /**
             * current file check
             *
             * @param currentFile
             */
            this.isCurrentFile           = function(currentFile) {
                var ret;
                
                if (currentFile)
                    ret = this.isContainClass(currentFile, CURRENT_FILE);
                
                return ret;
            };
            
            /**
             * selected file check
             *
             * @param currentFile
             */
            this.isSelected              = function(pSelected) {
                var ret;
                
                if (pSelected)
                    ret = this.isContainClass(pSelected, SELECTED_FILE);
                
                return ret;
            };
            
            /**
             * check is current file is a directory
             *
             * @param currentFile
             */
            this.isCurrentIsDir            = function(currentFile) {
                var current    = currentFile || this.getCurrentFile(),
                    fileType   = this.getByDataName('js-type', current),
                    ret        = this.isContainClass(fileType, 'directory');
                
                return ret;
            };
            
           /**
             * get link from current (or param) file
             *
             * @param currentFile - current file by default
             */
            this.getCurrentLink          = function(currentFile) {
                var current = currentFile || this.getCurrentFile(),
                    link    = this.getByTag('a', current);
                
                return link[0];
            };
            
            /**
             * get link from current (or param) file
             *
             * @param currentFile - current file by default
             */
            this.getCurrentPath          = function(currentFile) {
                var current     = currentFile || DOM.getCurrentFile(),
                    element     = DOM.getByTag('a', current)[0],
                    path        = element.getAttribute('href');
                
                path            = path.replace(CloudFunc.FS, '');
                
                return path;
            };
            
            /**
             * get name from current (or param) file
             *
             * @param currentFile
             */
            this.getCurrentName          = function(currentFile) {
                var link,
                    name        = '',
                    current     = currentFile || this.getCurrentFile();
                
                if (current)
                    link        = this.getCurrentLink(current);
                    
                if (link) {
                    name = link.title;
                }
                
                return name;
            };
            
            this.getFilenames           = function(allFiles) {
                var first, name,
                    files,
                    slice   = Array.prototype.slice,
                    names   = [];
                
                if (!allFiles)
                    throw Error('AllFiles could not be empty');
                
                files           = slice.call(allFiles);
                first           = files[0];
                
                if (first) {
                    name        = this.getCurrentName(first);
                } else {
                    first       = this.getCurrentFile();
                    name        = this.getCurrentName(first);
                }
                
                if (name === '..')
                    files.shift();
                
                names = files.map(function(current) {
                    return DOM.getCurrentName(current);
                });
                
                return names;
            };
            
            /**
             * set name from current (or param) file
             *
             * @param name
             * @param current
             */
            this.setCurrentName          = function(name, current) {
                var Info        = CurrentInfo,
                    link        = Info.link,
                    FS          = CloudFunc.FS,
                    dir         = FS + Info.dirPath;
                
                link.title      = name;
                link.innerHTML  = CloudFunc.Entity.encode(name);
                link.href       = dir + name;
                
                current.setAttribute('data-name', 'js-file-' + name);
                
                return link;
            };
            
            /**
             * check storage hash
             */
            this.checkStorageHash       = function(name, callback) {
                var Storage         = DOM.Storage,
                    parallel        = Util.exec.parallel,
                    loadHash        = DOM.loadCurrentHash,
                    nameHash        = name + '-hash',
                    getStoreHash    = Util.exec.with(Storage.get, nameHash);
                
                Util.check(arguments, ['name', 'callback']);
                
                parallel([loadHash, getStoreHash], function(error, loadHash, storeHash) {
                    var equal,
                        isContain   = /error/.test(loadHash);
                    
                    if (isContain)
                        error = loadHash;
                    else if (loadHash === storeHash)
                        equal = true;
                    
                    callback(error, equal, loadHash);
                });
            };
            
            /**
             * save data to storage
             *
             * @param name
             * @param data
             * @param callback
             */
            this.saveDataToStorage = function(name, data, hash, callback) {
                DOM.Files.get('config', function(error, config) {
                    var allowed     = config.localStorage,
                        isDir       = DOM.isCurrentIsDir(),
                        nameHash    = name + '-hash',
                        nameData    = name + '-data';
                    
                    if (!allowed || isDir)
                        Util.exec(callback);
                    else
                        Util.exec.if(hash, function() {
                            var Storage = DOM.Storage;
                            
                            Storage.set(nameHash, hash);
                            Storage.set(nameData, data);
                            
                            Util.exec(callback, hash);
                        }, function(callback) {
                            DOM.loadCurrentHash(function(error, loadHash) {
                                hash = loadHash;
                                callback();
                            });
                        });
                });
            };
            
            /**
             * save data to storage
             *
             * @param name
             * @param data
             * @param callback
             */
            this.getDataFromStorage = function(name, callback) {
                DOM.Files.get('config', function(error, config) {
                    var Storage     = DOM.Storage,
                        nameHash    = name + '-hash',
                        nameData    = name + '-data',
                        allowed     = config.localStorage,
                        isDir       = DOM.isCurrentIsDir();
                    
                    if (!allowed || isDir)
                        Util.exec(callback);
                    else {
                        Util.exec.parallel([
                            function(callback) {
                                Storage.get(nameData, callback);
                            },
                            function(callback) {
                                Storage.get(nameHash, callback);
                            }
                        ], callback);
                    }
                });
            };
            
            /** function getting FM
             * @param pPanel_o = {active: true}
             */
            this.getFM                   = function() {
                return this.getPanel().parentElement;
            };
            
            /** function getting panel active, or passive
             * @param options = {active: true}
             */
            this.getPanel                = function(options) {
                var files, panel, isLeft,
                    dataName    = 'js-',
                    current     = this.getCurrentFile();
                
                if (!current) {
                    panel       = this.getByDataName('js-left');
                } else {
                    files       = current.parentElement,
                    panel       = files.parentElement,
                    isLeft      = panel.getAttribute('data-name') === 'js-left';
                }
                    
                /* if {active : false} getting passive panel */
                if (options && !options.active) {
                    dataName    += isLeft ? 'right' : 'left';
                    panel       = this.getByDataName(dataName);
                }
                
                /* if two panels showed
                 * then always work with passive
                 * panel
                 */
                if (window.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH)
                    panel = this.getByDataName('js-left');
                    
                
                if (!panel)
                    throw Error('can not find Active Panel!');
                    
                return panel;
            };
            
            this.getFiles               = function(element) {
                var files   = DOM.getByDataName('js-files', element),
                    ret     = files.children || [];
                
                return ret;
            };
            
            /**
             * shows panel right or left (or active)
             */
            this.showPanel               = function(active) {
                var ret     = true,
                    panel   = this.getPanel({active: active});
                                
                if (panel)
                    this.show(panel);
                else
                    ret = false;
                
                return ret;
            };
            
            /**
             * hides panel right or left (or active)
             */
            this.hidePanel               = function(active) {
                var ret     = false,
                    panel   = this.getPanel({active: active});
                
                if (panel)
                    ret = this.hide(panel);
                
                return ret;
            };
                
            /**
             * open window with URL
             * @param url
             */
            this.openWindow              = function(url) {
                var left        = 140,
                    top         = 187,
                    width       = 1000,
                    height      = 650,
                    
                    options     = 'left='   + left          +
                        ',top='             + top           +
                        ',width='           + width         +
                        ',height='          + height        +
                        ',personalbar=0,toolbar=0'          +
                        ',scrollbars=1,resizable=1',
                    
                    wnd         = window.open(url, 'Cloud Commander Auth', options);
                
                if (!wnd)
                    DOM.Dialog.alert(TITLE, 'Please disable your popup blocker and try again.');
                
                return wnd;
            };
            
            /**
             * remove child of element
             * @param pChild
             * @param element
             */
            this.remove                  = function(child, element) {
                var parent = element || document.body;
                
                parent.removeChild(child);
                    
                return DOM;
            };
            
            /**
             * remove current file from file table
             * @param current
             *
             */
            this.deleteCurrent           = function(current) {
                var name, next, prev, parent, currentNew;
                
                if (!current)
                    Cmd.getCurrentFile();
                
                parent      = current && current.parentElement;
                name        = Cmd.getCurrentName(current);
                
                if (current && name !== '..') {
                    next    = current.nextSibling,
                    prev    = current.previousSibling;
                        
                    if (next)
                        currentNew = next;
                    else if (prev)
                        currentNew = prev;
                    
                    this.setCurrentFile(currentNew);
                    
                    parent.removeChild(current);
                }
                
                return currentNew;
            };
            
             /**
             * remove selected files from file table
             * @Selected
             */
            this.deleteSelected          = function(selected) {
                var i, n, current;
                
                if (!selected)
                    selected = this.getSelectedFiles();
                
                if (selected) {
                    n = selected.length;
                    
                    for (i = 0; i < n; i++) {
                        current = selected[i];
                        this.deleteCurrent(current);
                    }
                }
                
                return selected;
            };
            
            /**
             * rename current file
             *
             * @currentFile
             */
            this.renameCurrent            = function(current) {
                var from, dirPath, files, isExist,
                    RESTful     = DOM.RESTful,
                    Dialog      = DOM.Dialog;
                
                if (!Cmd.isCurrentFile(current))
                    current = Cmd.getCurrentFile();
                
                from        = Cmd.getCurrentName(current);
                
                if (from === '..')
                    Dialog.alert.noFiles(TITLE);
                else
                    Dialog.prompt(TITLE, 'Rename', from, {cancel: false}).then(function(to) {
                        isExist     = !!DOM.getCurrentByName(to);
                        dirPath     = Cmd.getCurrentDirPath();
                        
                        if (from !== to) {
                            files      = {
                                from    : dirPath + from,
                                to      : dirPath + to
                            };
                            
                            RESTful.mv(files, function(error) {
                                var Storage = DOM.Storage;
                                
                                if (!error) {
                                    DOM.setCurrentName(to, current);
                                    Cmd.updateCurrentInfo();
                                    Storage.remove(dirPath);
                                    
                                    if (isExist)
                                        CloudCmd.refresh();
                                }
                            });
                        }
                    });
            };
            
            /**
             * unified way to scrollIntoViewIfNeeded
             * (native suporte by webkit only)
             * @param element
             * @param pCenter
             */
            this.scrollIntoViewIfNeeded  = function(element, center) {
                var ret = element && element.scrollIntoViewIfNeeded;
                
                /* for scroll as small as possible
                 * param should be false
                 */
                if (arguments.length === 1)
                    center = false;
                
                if (ret)
                    element.scrollIntoViewIfNeeded(center);
                
                return ret;
            };
            
            /* scroll on one page*/
            this.scrollByPages           = function(element, pPages) {
               var ret = element && element.scrollByPages && pPages;
                
                if (ret)
                    element.scrollByPages(pPages);
                
                return ret;
            };
            
            this.changePanel            = function() {
                var dataName, files, current,
                    
                    panel           = DOM.getPanel(),
                    panelPassive    = DOM.getPanel({
                        active: false
                    }),
                    
                    name            = DOM.getCurrentName(),
                    
                    filesPassive    = DOM.getFiles(panelPassive);
                
                dataName            = panel.getAttribute('data-name');
                TabPanel[dataName]  = name;
                
                panel           = panelPassive;
                dataName        = panel.getAttribute('data-name');
                
                name            = TabPanel[dataName];
                
                if (name) {
                    current     = DOM.getCurrentByName(name, panel);
                    
                    if (current)
                        files       = current.parentElement;
                }
                
                if (!files || !files.parentElement) {
                    current     = DOM.getCurrentByName(name, panel);
                    
                    if (!current)
                        current = filesPassive[0];
                }
                
                DOM.setCurrentFile(current, {
                    history: true
                });
                
                return this;
            };
            
            this.goToDirectory          = function() {
                var msg     = 'Go to directory:',
                    path    = CurrentInfo.dirPath,
                    Dialog  = DOM.Dialog;
                
                Dialog.prompt(TITLE, msg, path, {cancel: false}).then(function(path) {
                    CloudCmd.loadDir({
                        path: path
                    });
                });
            },
            
            this.duplicatePanel         = function() {
                var isDir   = CurrentInfo.isDir,
                    path    = CurrentInfo.dirPath,
                    panel   = CurrentInfo.panelPassive;
                
                if (isDir)
                    path = CurrentInfo.path;
                
                CloudCmd.loadDir({
                    path: path,
                    panel: panel,
                    noCurrent: true
                });
            };
            
            this.swapPanels             = function() {
                var Info            = CurrentInfo,
                    panel           = Info.panel,
                    panelPassive    = Info.panelPassive,
                    
                    currentIndex    = [].indexOf.call(Info.files, Info.element),
                    
                    dirPath         = DOM.getCurrentDirPath(),
                    dirPathPassive  = DOM.getNotCurrentDirPath();
                
                 CloudCmd.loadDir({
                    path: dirPath,
                    panel: panelPassive,
                    noCurrent: true
                });
                
                CloudCmd.loadDir({
                    path: dirPathPassive,
                    panel: panel
                }, function() {
                    var el,
                        files   = Info.files,
                        length  = files.length - 1;
                    
                    if (currentIndex > length)
                        currentIndex = length;
                    
                    el = files[currentIndex];
                    
                    DOM.setCurrentFile(el);
                });
            };
            
            this.CurrentInfo            = CurrentInfo,
            
            this.updateCurrentInfo      = function(currentFile) {
                var info            = Cmd.CurrentInfo,
                    current         = currentFile || Cmd.getCurrentFile(),
                    files           = current.parentElement,
                    panel           = files.parentElement,
                    
                    panelPassive    = Cmd.getPanel({
                        active: false
                    }),
                    
                    filesPassive    = DOM.getFiles(panelPassive),
                    
                    name            = Cmd.getCurrentName(current);
                
                info.dir            = Cmd.getCurrentDirName();
                info.dirPath        = Cmd.getCurrentDirPath();
                info.parentDirPath  = Cmd.getParentDirPath();
                info.element        = current;
                info.ext            = Util.getExt(name);
                info.files          = files.children,
                info.filesPassive   = filesPassive,
                info.first          = files.firstChild;
                info.getData        = Cmd.getCurrentData;
                info.last           = files.lastChild;
                info.link           = Cmd.getCurrentLink(current);
                info.mode           = Cmd.getCurrentMode(current);
                info.name           = name;
                info.path           = Cmd.getCurrentPath(current);
                info.panel          = panel;
                info.panelPassive   = panelPassive;
                info.size           = Cmd.getCurrentSize(current);
                info.isDir          = Cmd.isCurrentIsDir();
                info.isSelected     = Cmd.isSelected(current);
                info.isOnePanel     =
                    info.panel.getAttribute('data-name') ===
                    info.panelPassive.getAttribute('data-name');
            };
        
        },
        
        DOMTree                     = Util.extendProto(DOMTreeProto),
        Images                      = Util.extendProto(ImagesProto);
    
    DOMProto                        = DOMFunc.prototype = new CmdProto();
    
    Util.extend(DOMProto, [
        DOMTree, {
            Images  : Images
        }
    ]);
    
    DOM                             = new DOMFunc();
    
})(Util, window);
