import * as XUI from './xxeclient/xui.js';
import * as XXE from './xxeclient/xxeclient.js';

// ---------------------------------------------------------------------------
// NewsResourceChooser
// ---------------------------------------------------------------------------

class NewsResourceChooser extends XUI.Dialog {
    static showDialog(newsItems, options) {
        return new Promise((resolve, reject) => { 
            let dialog = new NewsResourceChooser(newsItems, options, resolve);
            dialog.open();
        });
    }
    
    constructor(newsItems, options, resolve) {
        super({ title: ((options && options.title)?
                        options.title : "Choose News Resource"),
                movable: true, resizable: true, closeable: true,
                template: NewsResourceChooser.TEMPLATE,
                buttons: [ { label: "Cancel", action: "cancelAction" },
                           { label: "OK", action: "okAction",
                             default: true } ] });
        
        this._resolve = resolve;
        
        this._imageList = this.contentPane.querySelector("select");
        XUI.Util.removeAllChildren(this._imageList);
        
        for (let item of newsItems) {
            let date = null;
            if (item.imageURL !== null &&
                (date =
                 item.imageURL.match(/^(\d\d\d\d-\d\d-\d\d)\//)) !== null) {
                date = date[1];
                
                let optgroup = document.createElement("optgroup");
                optgroup.setAttribute("label", `${item.title} (${date})`);
                this._imageList.appendChild(optgroup);

                let opt = new Option("\u2022 Thumbnail", `${date}.png`,
                                     /*defaultSel*/ false, /*sel*/ false);
                optgroup.appendChild(opt);

                let imageName = item.imageURL;
                let slash = imageName.lastIndexOf('/');
                if (slash > 0 && slash+1 < imageName.length) {
                    imageName = imageName.substring(slash+1);
                }
                opt = new Option(`\u2022 ${imageName}`, item.imageURL,
                                 /*defaultSel*/ false, /*sel*/ false);
                optgroup.appendChild(opt);
            }
        }
        
        this._imageList.disabled = false;
    }
    
    dialogClosed(result) {
        // Close icon clicked ==> null result, which is just fine.
        this._resolve(result);
    }
    
    cancelAction() {
        this.close(null);
    }
    
    okAction() {
        // HTMLSelectElement.value is "" when there is no selection.
        let sel = this._imageList.value;
        if (!sel) {
            setTimeout(() => { this._imageList.focus(); }, 100 /*ms*/);
            return;
        }
        
        this.close(NewsStorage.uriPrefix + sel);
    }
}
NewsResourceChooser.TEMPLATE = document.createElement("template");
NewsResourceChooser.TEMPLATE.innerHTML = `
<div class="nrc-pane">
  <div class="nrc-head">XMLmind Software News</div>
  <div class="nrc-body"><select disabled="disabled" size="10">
    <option value="">Please choose an image.</option>
  </select></div>
</div>
`;

// ---------------------------------------------------------------------------
// NewsResource, NewsStorage
// ---------------------------------------------------------------------------

class NewsResource extends XXE.Resource {
    constructor(uri, data) {
        super(uri, data);
    }
}

class NewsStorage extends XXE.ResourceStorage {
    constructor(xmlEditor, newsItems) {
        super(xmlEditor);
        this._newsItems = newsItems;
    }

    async loadResource(uri) {
        if (!uri.startsWith(NewsStorage.uriPrefix)) {
            // Unknown URI.
            return null;
        }
        const url = NewsStorage.baseURI +
              uri.substring(NewsStorage.uriPrefix.length);
        
        let data = null;
        let loadError = null;
        try {
            let resp = await fetch(url);
            if (!resp.ok) {
                throw new Error(`ERROR ${resp.status} ${resp.statusText}`);
            }
            
            data = await resp.blob();
        } catch (error) {
            loadError = error;
        }
        if (loadError !== null) {
            throw new Error(
                `Cannot load resource "${uri}" (${url}):\n${loadError}`);
        }

        return new NewsResource(uri, data);
    }
    
    async openResource(options) {
        let uri = await NewsResourceChooser.showDialog(this._newsItems,
                                                       options);
        if (uri === null) {
            // Canceled by user.
            return null;
        }
        
        return this.loadResource(uri);
    }

    // storeResource default implementation, which throws an error, is OK
    // because we don't need it for this demo.
}
NewsStorage.hostURI = "https://www.xmlmind.com";
NewsStorage.baseURI = `${NewsStorage.hostURI}/news/`;
NewsStorage.uriPrefix = "xmnews:///";

// ---------------------------------------------------------------------------
// NewsItem
// ---------------------------------------------------------------------------

class NewsItem {
    constructor(title, htmlSource, date, imageURL) {
        this._title = title;
        this._htmlSource = htmlSource;
        this._date = date;
        this._imageURL = imageURL;
        
        this._uri = `${NewsStorage.uriPrefix}${encodeURIComponent(title)}.html`;
    }

    get title() {
        return this._title;
    }

    get htmlSource() {
        return this._htmlSource;
    }

    set htmlSource(source) {
        this._htmlSource = source;
    }

    get date() {
        return this._date;
    }

    get imageURL() {
        return this._imageURL;
    }

    get uri() {
        return this._uri;
    }
    
    static fromRSS(rssItem) {
        let title = null;
        let htmlFragment = null;
        let date = null;
        let imageURL = null;
        
        let child = rssItem.firstElementChild;
        while (child !== null) {
            switch (child.localName) {
            case "title":
                title = child.textContent.trim();
                break;
            case "description":
                htmlFragment = child.textContent.trim();
                break;
            case "pubDate":
                date = child.textContent.trim();
                break;
            case "enclosure":
                {
                    let url = child.getAttribute("url");
                    let type = child.getAttribute("type");
                    if (url && (url = url.trim()).length > 0 &&
                        type && (type = type.trim()).startsWith("image/")) {
                        imageURL = url;
                    }
                }
                break;
            }
            
            child = child.nextElementSibling;
        }

        if (title && htmlFragment && date) {
            const htmlTitle = XUI.Util.escapeHTML(title);
            const htmlSource =
`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <base href="${NewsStorage.baseURI}" />
    <title>${htmlTitle}</title>
    <link href="${NewsStorage.hostURI}/xmdecor/xmlmind_styles.css" 
          rel="stylesheet" type="text/css" />
  </head>
  <body><h1 class="xmtitle">${htmlTitle}</h1>${htmlFragment}</body>
</html>`;
        
            return new NewsItem(title, htmlSource, date, imageURL);
        } else {
            return null;
        }
    }
}

// ---------------------------------------------------------------------------
// NewsApp
// ---------------------------------------------------------------------------

export class NewsApp {
    constructor() {
        this._itemList = document.getElementById("itemList");
        this._itemList.disabled = true;
        this._itemList.onchange = this.itemSelected.bind(this);
        
        this._viewButton = document.getElementById("viewButton");
        this._viewButton.disabled = true;
        this._viewButton.onclick = this.viewItem.bind(this);
        
        this._editButton = document.getElementById("editButton");
        this._editButton.disabled = true;
        this._editButton.onclick = this.editItem.bind(this);
        
        this._saveButton = document.getElementById("saveButton");
        this._saveButton.disabled = true;
        this._saveButton.onclick = this.saveItem.bind(this);
        
        this._closeButton = document.getElementById("closeButton");
        this._closeButton.disabled = true;
        this._closeButton.onclick = this.closeItem.bind(this);

        this._xmlEditor = document.getElementById("xmlEditor");
        this._xmlEditor.addEventListener("saveStateChanged",
                                         this.itemSaved.bind(this));
        
        this._xmlEditor.autoRecover = false;
        // Unlike XMLEditorApp, this does NOT correspond to best practice. See
        // https://developer.chrome.com/docs/web-platform/page-lifecycle-api
        //     #the_beforeunload_event
        window.addEventListener("beforeunload", (event) => {
            if (this._xmlEditor.saveNeeded) {
                event.preventDefault();
                // Legacy support for older browsers.
                // A function that returns true if the page has unsaved changes.
                return (event.returnValue = true);
            }
        });
        
        this._items = [];
        this.loadNews(NewsStorage.baseURI + "xmlmind.xml");
    }

    async loadNews(rssURL) {
        let rssSource = null;
        let loadError = null;
        try {
            let resp = await fetch(rssURL);
            if (!resp.ok) {
                throw new Error(`ERROR ${resp.status} ${resp.statusText}`);
            }
            
            rssSource = await resp.text();
        } catch (error) {
            loadError = error;
        }
        if (loadError !== null) {
            XUI.Alert.showError(`Cannot load "${rssURL}":\n${loadError}`);
            return;
        }

        // ---
        
        let firstRSSItem = null;
        let rssDoc = (new DOMParser()).parseFromString(rssSource,
                                                       "application/xml");
        let rss = rssDoc.documentElement;
        if (rss.localName === "rss") {
            let channel = rss.firstElementChild;
            if (channel.localName === "channel") {
                let child = channel.firstElementChild;
                while (child !== null) {
                    if (child.localName === "item") {
                        firstRSSItem = child;
                        break;
                    }
                    
                    child = child.nextElementSibling;
                }
            }
        }
        
        // ---

        let items = [];
        
        let rssItem = firstRSSItem;
        while (rssItem !== null) {
            let item = NewsItem.fromRSS(rssItem);
            if (item !== null) {
                items.push(item);
            }
            
            rssItem = rssItem.nextElementSibling;
        }

        if (items.length === 0) {
            XUI.Alert.showError(
                `File "${rssURL}"\ndoes not contain any news item.`);
            return;
        }

        // ---

        this._items = items;
        this._xmlEditor.resourceStorage =
            new NewsStorage(this._xmlEditor, items);
        
        XUI.Util.removeAllChildren(this._itemList);
        for (let i = 0; i < items.length; ++i) {
            let item = items[i];
            let option = new Option(`${item.title} (${item.date})`,
                                    /*value*/ item.uri,
                                    /*defaultSelected*/ false,
                                    /*selected*/ false);
            this._itemList.appendChild(option);
        }
        
        this._itemList.disabled = false;
        this._viewButton.disabled = this._editButton.disabled =
            this._saveButton.disabled = this._closeButton.disabled = true;
    }

    itemSelected(event) {
        this.enableButtons();
    }

    enableButtons() {
        let sel = this._itemList.selectedIndex;
        if (sel < 0) {
            this._viewButton.disabled = this._editButton.disabled =
                this._saveButton.disabled = this._closeButton.disabled = true;
        } else {
            this._viewButton.disabled = this._editButton.disabled = false;
            
            const selItem = this._items[sel];
            if (this._xmlEditor.documentIsOpened &&
                this._xmlEditor.documentURI === this._itemList.value) {
                this._closeButton.disabled = false;
                this._saveButton.disabled = !this._xmlEditor.saveNeeded;
            } else {
                this._closeButton.disabled = this._saveButton.disabled = true;
            }
        }
    }
    
    viewItem(event) {
        this.openItem(/*readOnly*/ true);
    }

    editItem(event) {
        this.openItem(/*readOnly*/ false);
    }

    async openItem(readOnly) {
        let sel = this._itemList.selectedIndex;
        if (sel < 0) {
            return;
        }
        const selItem = this._items[sel];
        
        let confirmed = await NewsApp.confirmDiscardChanges(this._xmlEditor);
        if (!confirmed) {
            return;
        }

        let closed = await NewsApp.closeDocument(this._xmlEditor);
        if (!closed) {
            return;
        }

        let opened = await this._xmlEditor.openDocument(selItem.htmlSource,
                                                        selItem.uri, readOnly);
        if (!opened) {
            return;
        }
        
        this.enableButtons();
    }
    
    static confirmDiscardChanges(xmlEditor) {
        if (!xmlEditor.documentIsOpened || !xmlEditor.saveNeeded) {
            // No changes.
            return Promise.resolve(true);
        }

        return XUI.Confirm.showConfirm(
            `"${xmlEditor.documentURI}" has been modified\nDiscard changes?`);
    }

    static closeDocument(xmlEditor) {
        if (!xmlEditor.documentIsOpened) {
            return Promise.resolve(true);
        }

        return xmlEditor.closeDocument(/*discardChanges*/ true);
    }
    
    async saveItem(event) {
        if (!this._xmlEditor.documentIsOpened || !this._xmlEditor.saveNeeded) {
            return;
        }

        let savedItem = this.findItem(this._xmlEditor.documentURI);
        if (savedItem === null) {
            // Should not happen.
            return;
        }
        
        const htmlSource = await this._xmlEditor.getDocument();
        if (htmlSource === null) {
            return;
        }
        savedItem.htmlSource = htmlSource;
        
        let saved = await this._xmlEditor.saveDocument();
        if (!saved) {
            return;
        }
        // No need to enableButtons, there is itemSaved.
        
        let newWin = window.open("", "_blank");
        newWin.document.write(htmlSource);
        newWin.document.close();
    }

    findItem(docURI) {
        for (let item of this._items) {
            if (item.uri === docURI) {
                return item;
            }
        }
        return null;
    }
    
    async closeItem(event) {
        let confirmed = await NewsApp.confirmDiscardChanges(this._xmlEditor);
        if (!confirmed) {
            return;
        }
        
        let closed = await NewsApp.closeDocument(this._xmlEditor);
        if (!closed) {
            return;
        }
        
        this._itemList.selectedIndex = -1;
        this.enableButtons();
    }
    
    itemSaved(event) {
        this.enableButtons();
    }
}
