'use strict';

/* global DOM */

const Emitify = require('emitify/legacy');
const inherits = require('inherits');
const rendy = require('rendy/legacy');
const exec = require('execon');
const load = require('load.js');

const {kebabToCamelCase} = require('../common/util');
const isDev = process.env.NODE_ENV === 'development';

const Images = require('./dom/images');
const {
    unregisterSW,
} = require('./sw/register');

const join = require('join-io/www/join');
const jonny = require('jonny/legacy');
const currify = require('currify/legacy');

const bind = (f, ...a) => () => f(...a);
const noop = () => {};

const {
    apiURL,
    formatMsg,
    buildFromJSON,
} = require('../common/cloudfunc');

const loadModule = require('./load-module');

inherits(CloudCmdProto, Emitify);

module.exports = new CloudCmdProto(DOM);

load.addErrorListener((e, src) => {
    const msg = `file ${src} could not be loaded`;
    Images.show.error(msg);
});

function CloudCmdProto(DOM) {
    let Key;
    let Listeners;
    
    const log = (...a) => {
        if (!isDev )
            return;
        
        console.log(...a);
    };
    
    Emitify.call(this);
    
    const CloudCmd = this;
    const Info = DOM.CurrentInfo;
    const Storage = DOM.Storage;
    const Files = DOM.Files;
    
    this.log = log;
    this.PREFIX = '';
    this.PREFIX_URL = '';
    this.DIRCLIENT = '/dist/';
    this.DIRCLIENT_MODULES = this.DIRCLIENT + 'modules/';
    
    this.MIN_ONE_PANEL_WIDTH    = 1155;
    this.HOST                   = location.origin ||
                                  location.protocol + '//' + location.host;
    
    const TITLE = 'Cloud Commander';
    this.TITLE = TITLE;
    
    this.sort = {
        left: 'name',
        right: 'name',
    };
    
    this.order = {
        left: 'asc',
        right: 'asc',
    };
    
    /**
     * Функция привязываеться ко всем ссылкам и
     *  загружает содержимое каталогов
     *
     * @param params - {
     *          paramLink - ссылка
     *          needRefresh - необходимость обязательной загрузки данных с сервера
     *          panel
     *      }
     * @param callback
     */
    this.loadDir = (params, callback) => {
        const p = params;
        
        const refresh = p.isRefresh;
        const panel = p.panel;
        const history = p.history;
        const noCurrent = p.noCurrent;
        const currentName = p.currentName;
        
        let panelChanged;
        if (!noCurrent)
            if (panel && panel !== Info.panel) {
                DOM.changePanel();
                panelChanged = true;
            }
        
        let imgPosition;
        if (panelChanged || refresh || !history)
            imgPosition = 'top';
        
        Images.show.load(imgPosition, panel);
        
        /* загружаем содержимое каталога */
        ajaxLoad(p.path, {
            refresh,
            history,
            noCurrent,
            currentName,
        }, panel, callback);
    };
    
    /**
     * Конструктор CloudClient, который
     * выполняет весь функционал по
     * инициализации
     */
    this.init = (prefix, config) => {
        const func = bind(exec.series, [
            initModules,
            baseInit,
            loadPlugins,
            loadStyle,
            exec.with(CloudCmd.route, location.hash),
        ], noop);
        
        const funcBefore = (callback) => {
            const src = prefix + CloudCmd.DIRCLIENT_MODULES + 'polyfill.js';
            load.js(src, callback);
        };
        
        CloudCmd.PREFIX = prefix;
        CloudCmd.PREFIX_URL = prefix + apiURL;
        
        CloudCmd.config = (key) => config[key];
        CloudCmd.config.if = currify((key, fn, a) => config[key] && fn(a));
        CloudCmd._config = (key, value) => {
            /*
             * should be called from config.js only
             * after successful update on server
             */
             
            if (key === 'password')
                return;
            
            config[key] = value;
        };
        
        if (config.oneFilePanel)
            CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;
        
        exec.if(document.body.scrollIntoViewIfNeeded, func, funcBefore);
    };
    
    function loadStyle(callback) {
        const prefix = CloudCmd.PREFIX;
        const name = prefix + '/dist/cloudcmd.common.css';
        
        load.css(name, callback);
    }
    
    function loadPlugins(callback) {
        const prefix = CloudCmd.PREFIX;
        const plugins = prefix + '/plugins.js';
        
        load.js(plugins, callback);
    }
    
    this.join = (urls) => {
        const prefix  = CloudCmd.PREFIX;
        
        if (!Array.isArray(urls))
            throw Error('urls should be array!');
        
        const noPrefixUrls = urls.map((url) => {
            return url.replace(prefix, '');
        });
        
        return prefix + join(noPrefixUrls);
    };
    
    this.route = (path) => {
        const query = path.split('/');
        
        if (!path)
            return;
        
        const [kebabModule] = query;
        const module = kebabToCamelCase(kebabModule.slice(1));
        
        const file = query[1];
        const current = DOM.getCurrentByName(file);
        
        if (file && !current) {
            const msg = formatMsg('set current file', file, 'error');
            CloudCmd.log(msg);
            return;
        }
        
        DOM.setCurrentFile(current);
        CloudCmd.execFromModule(module, 'show');
    };
    
    this.logOut = () => {
        const url = CloudCmd.PREFIX + '/logout';
        const error = () => document.location.reload();
        
        DOM.Storage.clear();
        unregisterSW();
        DOM.load.ajax({
            url,
            error,
        });
    };
    
    function initModules(callback) {
        exec.if(CloudCmd.Key, () => {
            Key = new CloudCmd.Key();
            CloudCmd.Key = Key;
            Key.bind();
        }, (func) => {
            /* привязываем клавиши к функциям */
            const path = 'key.js';
            
            loadModule({
                path,
                func
            });
        });
        
        Files.get('modules', (error, modules) => {
            const showLoad = Images.show.load;
            
            const doBefore = {
                'edit': showLoad,
                'menu': showLoad,
            };
            
            const load = (name, path, dobefore) => {
                loadModule({
                    name,
                    path,
                    dobefore,
                });
            };
            
            if (!modules)
                modules = [];
            
            modules.local.forEach((module) => {
                load(null, module, doBefore[module]);
            });
            
            callback();
        });
    }
    
    function baseInit(callback) {
        const files = DOM.getFiles();
        
        CloudCmd.on('current-file', DOM.updateCurrentInfo);
        
        /* выделяем строку с первым файлом */
        if (files)
            DOM.setCurrentFile(files[0], {
                // when hash is present
                // it should be handled with this.route
                // overwre otherwise
                history: !location.hash
            });
        
        const dirPath = DOM.getCurrentDirPath();
        Listeners = CloudCmd.Listeners;
        Listeners.init();
        
        const panels = getPanels();
        panels.forEach(Listeners.setOnPanel);
        
        Listeners.initKeysPanel();
        
        if (!CloudCmd.config('dirStorage'))
            return callback();
        
        Storage.get(dirPath, (error, data) => {
            if (!data) {
                data = getJSONfromFileTable();
                Storage.set(dirPath, data);
            }
            callback();
        });
    }
    
    function getPanels() {
        const panels = ['left'];
        
        if (CloudCmd.config('oneFilePanel'))
            return panels;
        
        return [
            ...panels,
            'right',
        ];
    }
    
    this.execFromModule = async (moduleName, funcName, ...args) => {
        await CloudCmd[moduleName]();
        
        const func = CloudCmd[moduleName][funcName];
        func(...args);
    };
    
    this.refresh = (options = {}, callback) => {
        if (!callback && typeof options === 'function') {
            callback = options;
            options = {};
        }
       
        const panel = options.panel || Info.panel;
        const path = DOM.getCurrentDirPath(panel);
        
        const isRefresh = true;
        const history = false;
        const noCurrent = options ? options.noCurrent : false;
        const currentName = options.currentName;
        
        CloudCmd.loadDir({
            path,
            isRefresh,
            history,
            panel,
            noCurrent,
            currentName,
        }, callback);
    };
    
    /**
     * Функция загружает json-данные о Файловой Системе
     * через ajax-запрос.
     * @param path - каталог для чтения
     * @param options
     * { refresh, history } - необходимость обновить данные о каталоге
     * @param panel
     * @param callback
     *
     */
    function ajaxLoad(path, options, panel, callback) {
        const create = (error, json) => {
            const RESTful = DOM.RESTful;
            const name = options.currentName || Info.name;
            const obj = jonny.parse(json);
            const isRefresh = options.refresh;
            const noCurrent = options.noCurrent;
            
            if (!isRefresh && json)
                return createFileTable(obj, panel, options, callback);
            
            const position = DOM.getPanelPosition(panel);
            const sort = CloudCmd.sort[position];
            const order = CloudCmd.order[position];
            
            const query = rendy('?sort={{ sort }}&order={{ order }}', {
                sort,
                order,
            });
            
            RESTful.read(path + query, 'json', (error, obj) => {
                if (error)
                    return;
                
                options.sort = sort;
                options.order = order;
                
                createFileTable(obj, panel, options, () => {
                    if (isRefresh && !noCurrent)
                        DOM.setCurrentByName(name);
                    
                    exec(callback);
                });
                
                if (!CloudCmd.config('dirStorage'))
                    return;
                
                Storage.set(path, obj);
            });
        };
        
        if (!options)
            options    = {};
        
        CloudCmd.log('reading dir: "' + path + '";');
        
        if (!CloudCmd.config('dirStorage'))
            return create();
        
        Storage.get(path, create);
    }
    
    /**
     * Функция строит файловую таблицу
     * @param json  - данные о файлах
     * @param panelParam
     * @param history
     * @param callback
     */
    function createFileTable(json, panelParam, options, callback) {
        const {
            history,
            noCurrent,
        } = options;
        
        const names = ['file', 'path', 'link', 'pathLink'];
        
        Files.get(names, (error, templFile, templPath, templLink, templPathLink) => {
            const Dialog = DOM.Dialog;
            const panel = panelParam || DOM.getPanel();
            
            const {
                dir,
                name,
            } = Info;
            
            if (error)
                return Dialog.alert(TITLE, error.responseText);
            
            const childNodes = panel.childNodes;
            let i = childNodes.length;
            
            while (i--)
                panel.removeChild(panel.lastChild);
            
            panel.innerHTML = buildFromJSON({
                sort        : options.sort,
                order       : options.order,
                data        : json,
                id          : panel.id,
                prefix      : CloudCmd.PREFIX,
                template    : {
                    file        : templFile,
                    path        : templPath,
                    pathLink    : templPathLink,
                    link        : templLink
                }
            });
            
            Listeners.setOnPanel(panel);
            
            if (!noCurrent) {
                let current;
                
                if (name === '..' && dir !== '/')
                    current = DOM.getCurrentByName(dir);
                
                if (!current)
                    current = DOM.getFiles(panel)[0];
                
                DOM.setCurrentFile(current, {
                    history,
                });
                
                CloudCmd.emit('active-dir', Info.dirPath);
            }
            
            exec(callback);
        });
    }
    
    /**
     * Функция генерирует JSON из html-таблицы файлов и
     * используеться при первом заходе в корень
     */
    function getJSONfromFileTable() {
        const path = DOM.getCurrentDirPath();
        const infoFiles = Info.files || [];
        
        const notParent = (current) => {
            const name = DOM.getCurrentName(current);
            return name !== '..';
        };
        
        const parse = (current) => {
            const name = DOM.getCurrentName(current);
            const size = DOM.getCurrentSize(current);
            const owner = DOM.getCurrentOwner(current);
            const mode = DOM.getCurrentMode(current);
            const date = DOM.getCurrentDate(current);
            
            return {
                name,
                size,
                mode,
                owner,
                date,
            };
        };
        
        const files = infoFiles
            .filter(notParent)
            .map(parse);
        
        const fileTable = {
            path,
            files,
        };
        
        return fileTable;
    }
    
    this.goToParentDir = () => {
        const dir = Info.dir;
        const {
            dirPath,
            parentDirPath,
        } = Info;
        
        if (dirPath === parentDirPath)
            return;
        
        const path = parentDirPath;
        
        CloudCmd.loadDir({path}, () => {
            const {panel} = Info;
            const current = DOM.getCurrentByName(dir);
            const first = DOM.getFiles(panel)[0];
            
            DOM.setCurrentFile(current || first, {
                history
            });
        });
    };
}

