import { unescapeHtml } from './util' import { StringReader } from './string_reader'

const SELF_CLOSING_TAGS = [

'area',
'base',
'br',
'embed',
'hr',
'iframe',
'img',
'input',
'link',
'meta',
'param',
'source',
'track'

]

const TEXT_ONLY_TAGS = [

'script',
'style'

]

class CloseTag {

constructor(type){
    this.type = type
}

}

export class VirtualNode {

static fromString(html){
    const out = new this()
    out.appendHtml(html)
    out.normalize()
    return out
}

constructor(parent = null, type = '#fragment', attributes = {}){
    this.parent = parent
    this.type = type
    this.attributes = attributes
    this.children = []
}

appendNode(type, attributes = {}){
    const out = new this.constructor(this, type, attributes)
    this.children.push(out)
    return out
}

appendHtml(html){
    if(!(html instanceof StringReader)){
        html = new StringReader(html)

        while(html.length > 0){
            try {
                this.appendHtml(html)
            } catch(e){
                if(e instanceof CloseTag){
                    //do nothing
                } else {
                    throw e
                }
            }
        }

        return this
    }

    while(html.length > 0){
        let matches;

        if(matches = html.match(/^[^<]+/)){
            this.appendNode('#text', {value: matches[0]})
        } else if(matches = html.match(/^<!DOCTYPE[^>]*>/i)){
            if(!this.parent){
                this.appendNode('#doctype')
            }
        } else if(matches = html.match(/^<!--([\s\S]*?)-->/i)){
            this.appendNode('#comment', {value: matches[1]})
        } else if(matches = html.match(/^<([^>\s]+)/)){
            const type = matches[1].toLowerCase()
            const attributes = {}

            while(html.length > 0){
                if(matches = html.match(/^\s*([\w-]+)\s*=\s*\"([^\">]*)\"/)){
                    attributes[matches[1]] = unescapeHtml(matches[2])
                } else if(matches = html.match(/^\s*([\w-]+)\s*=\s*\'([^\'>]*)\'/)){
                    attributes[matches[1]] = unescapeHtml(matches[2])
                } else if(matches = html.match(/^\s*([\w-]+)\s*=\s*([^\s>]+)/)){
                    attributes[matches[1]] = unescapeHtml(matches[2])
                } else if(matches = html.match(/^\s*([\w-]+)/)){
                    attributes[matches[1]] = null
                } else {
                    html.match(/^[^>]*>/)
                    break
                }
            }

            if(matches = type.match(/^\/(.*)/)){
                throw new CloseTag(matches[1])
            }

            const child = this.appendNode(type, attributes)

            if(SELF_CLOSING_TAGS.includes(type)){
                // do nothing
            } else if(TEXT_ONLY_TAGS.includes(type) && (matches = html.match(new RegExp(`^([\\s\\S]*?)<\\/${type}[^>]*>`)))){
                child.appendNode('#text', {value: matches[1]})
            } else if(TEXT_ONLY_TAGS.includes(type) && (matches = html.match(/^([\s\S]+)/))){
                child.appendNode('#text', {value: matches[1]})
            } else {
                try {
                    child.appendHtml(html)
                } catch(e){
                    if(e instanceof CloseTag && e.type == type){
                        //do nothing
                    } else {
                        throw e
                    }
                }
            }
        } else if(matches = html.match(/^[\s\S]/)) {
            this.appendNode('#text', {value: matches[0]})
        } else {
            break;
        }
    }

}

normalize(){
    if(!this.parent && this.children.some(child => child.type == 'html')){
        this.children = [
            new this.constructor(this, '#doctype'),
            ...this.children.filter(child => child.type == 'html')
        ]
    }

    if(this.type == '#text'){
        this.attributes.value = this.attributes.value.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
    }

    if(this.parent && this.parent.type == 'textarea' && this.type == '#text'){
        this.attributes.value = this.attributes.value.replace(/^\n/, '')
    }

    this.children.forEach(child => child.normalize())
}

}