import { App, Editor, MarkdownView, Plugin, PluginSettingTab, Setting, moment, normalizePath, TAbstractFile, FileSystemAdapter, ListedFiles, TFile } from 'obsidian';
import * as Path from 'path';

interface CustomAttachmentLocationSettings {
    attachmentFolderPath: string;
    pastedImageFileName: string;
    dateTimeFormat: string;
    autoRenameFolder: boolean;
    autoRenameFiles: boolean;
}

const DEFAULT_SETTINGS: CustomAttachmentLocationSettings = {
    attachmentFolderPath: './assets/${filename}',
    pastedImageFileName: 'image-${date}',
    dateTimeFormat: 'YYYYMMDDHHmmssSSS',
    autoRenameFolder: true,
    autoRenameFiles: false
}

let originalSettings = {
    attachmentFolderPath: ''
};

const blobToArrayBuffer = (blob: Blob) => {
    return new Promise((resolve) => {
      const reader = new FileReader()
      reader.onloadend = () => resolve(reader.result)
      reader.readAsArrayBuffer(blob)
    })
}


class TemplateString extends String{
    interpolate(params: Object) {
        const names = Object.keys(params);
        const vals = Object.values(params);
        return new Function(...names, `return \`${this}\`;`)(...vals);
    }
}


export default class CustomAttachmentLocation extends Plugin {
    settings: CustomAttachmentLocationSettings;
    useRelativePath: boolean = false;
    adapter: FileSystemAdapter;

    async onload() {
        console.log('loading plugin');

        this.adapter = this.app.vault.adapter as FileSystemAdapter;
        await this.loadSettings();
        this.backupConfigs();

        this.addSettingTab(new CustomAttachmentLocationSettingTab(this.app, this));
        /*
            bind this pointer to handlePaste
            this.registerEvent(this.app.workspace.on('editor-paste', this.handlePaste));
        */
        this.registerEvent(this.app.workspace.on('editor-paste', this.handlePaste.bind(this)));
        this.registerEvent(this.app.workspace.on('editor-drop', this.handleDrop.bind(this)));
        this.registerEvent(this.app.workspace.on('file-open', this.handleFileOpen.bind(this)));

        this.registerEvent(this.app.vault.on('rename', this.handleRename.bind(this)));


    }

    onunload() {
        console.log('unloading plugin');
        this.restoreConfigs();
    }

    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
        if(this.settings.attachmentFolderPath.startsWith('./'))
            this.useRelativePath = true;
        else
            this.useRelativePath = false;
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }

    backupConfigs(){
        //@ts-ignore
        originalSettings.attachmentFolderPath = this.app.vault.getConfig('attachmentFolderPath');
    }

    restoreConfigs(){
        //@ts-ignore
        this.app.vault.setConfig('attachmentFolderPath', originalSettings.attachmentFolderPath);
    }

    updateAttachmentFolderConfig(path: string)
    {
        //@ts-ignore
        this.app.vault.setConfig('attachmentFolderPath', path);
    }

    getAttachmentFolderPath(mdFileName: string)
    {
        let path = new TemplateString(this.settings.attachmentFolderPath).interpolate({
            filename: mdFileName
        });
        return path;
    }

    getAttachmentFolderFullPath(mdFolderPath: string, mdFileName: string)
    {
        let attachmentFolder = '';

        if(this.useRelativePath)
            attachmentFolder = Path.join(mdFolderPath, this.getAttachmentFolderPath(mdFileName));
        else
        {
            attachmentFolder = this.getAttachmentFolderPath(mdFileName);
        }
        return normalizePath(attachmentFolder);
    }

    getPastedImageFileName(mdFileName: string)
    {
        let datetime = moment().format(this.settings.dateTimeFormat);
        let name = new TemplateString(this.settings.pastedImageFileName).interpolate({
            filename: mdFileName,
            date: datetime
        });
        return name;
    }


    async handlePaste(event: ClipboardEvent, editor: Editor, view: MarkdownView){
        console.log('Handle Paste');

        let mdFileName = view.file.basename;
        let mdFolderPath: string = Path.dirname(view.file.path);

        let path = this.getAttachmentFolderPath(mdFileName);
        let fullPath = this.getAttachmentFolderFullPath(mdFolderPath, mdFileName);

        this.updateAttachmentFolderConfig(path);
        // this.app.vault.setConfig('attachmentFolderPath', `./assets/${filename}`);

        let clipBoardData = event.clipboardData;
        let clipBoardItems = clipBoardData.items;
        if(!clipBoardData.getData('text/plain')){
            for(let i in clipBoardItems){
                if(!clipBoardItems.hasOwnProperty(i))
                    continue;
                let item = clipBoardItems[i];
                if(item.kind !== 'file')
                    continue;
                if(!(item.type === 'image/png' || item.type === 'image/jpeg'))
                    continue;

                let pasteImage = item.getAsFile();
                if(!pasteImage)
                    continue;

                let extension = '';
                item.type === 'image/png' ? extension = 'png' : item.type === 'image/jpeg' && (extension = 'jpeg');

                event.preventDefault();

                //if folder not exist, mkdir first.
                if(!await this.adapter.exists(fullPath))
                    await this.adapter.mkdir(fullPath);

                let img = await blobToArrayBuffer(pasteImage);

                let name = this.getPastedImageFileName(mdFileName);
                // let name = 'image-' + moment().format('YYYYMMDDHHmmssSSS');


                //@ts-ignore
                let imageFile = await this.app.saveAttachment(name, extension, img);
                let markdownLink = await this.app.fileManager.generateMarkdownLink(imageFile, view.file.path);
                markdownLink += '\n\n';
                editor.replaceSelection(markdownLink);
            }
        }
    }

    async handleDrop(event: DragEvent, editor: Editor, view: MarkdownView){
        console.log('Handle Drop');

        let mdFileName = view.file.basename;
        let mdFolderPath: string = Path.dirname(view.file.path);

        let path = this.getAttachmentFolderPath(mdFileName);
        let fullPath = this.getAttachmentFolderFullPath(mdFolderPath, mdFileName);

        if(!this.useRelativePath && !await this.adapter.exists(fullPath))
            await this.app.vault.createFolder(fullPath);
        
        this.updateAttachmentFolderConfig(path);
    }

    async handleFileOpen(file: TFile | null){
        console.log('Handle File Open');
        
        if (file == null) {
            console.log("No file open");
            return;
        }

        let mdFileName = file.basename;

        let path = this.getAttachmentFolderPath(mdFileName);

        this.updateAttachmentFolderConfig(path);
    }

    async handleRename(newFile: TFile, oldFilePath: string){
        console.log('Handle Rename');

        //if autoRename is off or not a markdown file
        if(!this.settings.autoRenameFolder || newFile.extension !== 'md')
            return;

        let newName = newFile.basename;

        let oldName = Path.basename(oldFilePath, '.md');

        let mdFolderPath: string = Path.dirname(newFile.path);
        let oldMdFolderPath: string = Path.dirname(oldFilePath);
        let oldAttachmentFolderPath: string = this.getAttachmentFolderFullPath(oldMdFolderPath, oldName);
        let newAttachmentFolderPath: string = this.getAttachmentFolderFullPath(mdFolderPath, newName);

        //check if old attachment folder exists and is necessary to rename Folder
        if(await this.adapter.exists(oldAttachmentFolderPath) && (oldAttachmentFolderPath !== newAttachmentFolderPath))
        {
            let tfolder: TAbstractFile = this.app.vault.getAbstractFileByPath(oldAttachmentFolderPath);

            if(tfolder == null)
                return;

            let newAttachmentParentFolderPath: string = Path.dirname(newAttachmentFolderPath)
            if (!(await this.adapter.exists(newAttachmentParentFolderPath))) {
                await this.app.vault.createFolder(newAttachmentParentFolderPath);
            }

            await this.app.fileManager.renameFile(tfolder, newAttachmentFolderPath);

            let oldAttachmentParentFolderPath: string = Path.dirname(oldAttachmentFolderPath)
            let oldAttachmentParentFolderList: ListedFiles = await this.adapter.list(oldAttachmentParentFolderPath);
            if (oldAttachmentParentFolderList.folders.length === 0 && oldAttachmentParentFolderList.files.length === 0) {
              await this.adapter.rmdir(oldAttachmentParentFolderPath, true);
            }
    
            this.updateAttachmentFolderConfig(this.getAttachmentFolderPath(newName));
        }

        //if autoRenameFiles is off
        if(!this.settings.autoRenameFiles)
            return;

        let embeds = this.app.metadataCache.getCache(newFile.path)?.embeds;
        if(!embeds)
            return;

        let files: string[] = [];

        for(let embed of embeds)
        {
            let link = embed.link;
            if(link.endsWith('.png') || link.endsWith('jpeg'))
                files.push(Path.basename(link));
            else
                continue;

        }

        let attachmentFiles: ListedFiles= await this.adapter.list(newAttachmentFolderPath);
        for(let file of attachmentFiles.files)
        {
            console.log(file);
            let filePath = file;
            let fileName = Path.basename(filePath);
            if((files.indexOf(fileName) > -1) && fileName.contains(oldName))
            {
                fileName = fileName.replace(oldName, newName);
                let newFilePath = normalizePath(Path.join(newAttachmentFolderPath, fileName));
                let tfile = this.app.vault.getAbstractFileByPath(filePath);
                await this.app.fileManager.renameFile(tfile, newFilePath);
            }
            else
                continue;
        }
    }
}

class CustomAttachmentLocationSettingTab extends PluginSettingTab {
    plugin: CustomAttachmentLocation;

    constructor(app: App, plugin: CustomAttachmentLocation) {
        super(app, plugin);
        this.plugin = plugin;
    }

    display(): void {
        let {containerEl} = this;

        containerEl.empty();

        containerEl.createEl('h2', {text: 'Custom Attachment Location'});

        let el = new Setting(containerEl)
            .setName('Location for New Attachments')
            .setDesc('Start with "./" to use relative path. Available variables: ${filename}.(NOTE: DO NOT start with "/" or end with "/". )')
            .addText(text => text
                .setPlaceholder('./assets/${filename}')
                .setValue(this.plugin.settings.attachmentFolderPath)
                .onChange(async (value: string) => {
                    console.log('attachmentFolder: ' + value);
                    value = normalizePath(value);
                    console.log('normalized attachmentFolder: ' + value);

                    this.plugin.settings.attachmentFolderPath = value;
                    if(value.startsWith('./'))
                        this.plugin.useRelativePath = true;
                    else
                        this.plugin.useRelativePath = false;
                    await this.plugin.saveSettings();
                }));
        el.controlEl.addEventListener('change',  (()=>{this.display();}));


        new Setting(containerEl)
            .setName('Pasted Image Name')
            .setDesc('Available variables: ${filename}, ${date}.')
            .addText(text => text
                .setPlaceholder('image-${date}')
                .setValue(this.plugin.settings.pastedImageFileName)
                .onChange(async (value: string) => {
                    console.log('pastedImageFileName: ' + value);
                    this.plugin.settings.pastedImageFileName = value;
                    await this.plugin.saveSettings();
                }));

        new Setting(containerEl)
            .setName('Date Format')
            .setDesc('YYYYMMDDHHmmssSSS')
            .addMomentFormat(text => text
                .setDefaultFormat('YYYYMMDDHHmmssSSS')
                .setValue(this.plugin.settings.dateTimeFormat)
                .onChange(async (value: string) => {
                    console.log('dateTimeFormat: ' + value);
                    this.plugin.settings.dateTimeFormat = value || 'YYYYMMDDHHmmssSSS';
                    await this.plugin.saveSettings();
                }));


        new Setting(containerEl)
            .setName('Automatically rename attachment folder')
            .setDesc('When renaming md files, automatically rename attachment folder if folder name contains "${filename}".')
            .addToggle(toggle => toggle
                .setValue(this.plugin.settings.autoRenameFolder)
                .onChange(async (value: boolean) => {
                    this.plugin.settings.autoRenameFolder = value;
                    this.display();
                    await this.plugin.saveSettings();
                }));

        if(this.plugin.settings.autoRenameFolder)
            new Setting(containerEl)
            .setName('Automatically rename attachment files [Experimental]')
            .setDesc('When renaming md files, automatically rename attachment files if file name contains "${filename}".')
            .addToggle(toggle => toggle
                .setValue(this.plugin.settings.autoRenameFiles)
                .onChange(async (value: boolean) => {
                    this.plugin.settings.autoRenameFiles = value;
                    await this.plugin.saveSettings();
                }));
    }
}