class CCPExecutionForm extends HTMLElement{
  
  #boot;
  #rootdoc;
  #data;
  #method;
  #executionmonitor;
  
  #serviceurl; 
  
  constructor(){
    super()
    this.#boot = document.querySelector("d4s-boot-2")
    this.#rootdoc = this.attachShadow({ "mode" : "open"})
  }
  
  connectedCallback(){
    this.#serviceurl = this.getAttribute("serviceurl")
    this.connectNewExecutionRequest()
    this.render()

    const params = new URLSearchParams(window.location.search)
    if(params.get('execution')){
      const execution = { id : params.get('execution') }
      this.prepareFromExecution(execution) 
    } else if(params.get('method')){
      this.#method = params.get('method')
      this.loadMethod()
    } else {
      this.showMethod()
    }
  }

  static get observedAttributes() { 
    return ["method"];
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    //if((oldValue != newValue) && (name === "method")){
    if(name === "method"){
      this.#method = newValue
      this.loadMethod()
    }
  }
  
  connectNewExecutionRequest(){
    document.addEventListener("newexecutionrequest", ev=>{ 
      if(window.confirm("Please confirm overwrite of execution form?")){
        this.setAttribute("method", ev.detail)
        this.parentElement.scrollIntoViewIfNeeded() 
      }
    })
  }
  
  render(){
    this.#rootdoc.innerHTML = `
      <div>
        <link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
        <style>
          .ccp-execution-form{
            position: relative;
          }
        </style>
        <template id="EXECUTION_FORM_TEMPLATE">
          <div class="ccp-execution-form" name="execution_form">
            <h5 class="ccp-method-title"></h5>
            <p class="description font-italic font-weight-lighter"></p>
            <div class="plexiglass d-none"></div>
            <form name="execution_form" class="d-flex flex-column gap-3" style="gap:5px">
              <div class="card">
                <div class="card-header">
                  <h5>Inputs</h5>
                </div>
                <div class="card-body ccp-inputs">
                  <div>
                    <div class="form-group"></div>
                  </div>
                </div>        
              </div>
              <div class="card">
                <div class="card-header">
                  <h5>Options</h5>
                </div>
                <div class="card-body">
                  <div class="col form-check">
                    <input class="form-check-input" type="checkbox" name="auto-archive-outputs" alt="Automatically archive outputs to workspace" title="Automatically archive outputs to workspace" checked="checked">
                    <label class="form-check-label">Automatically archive outputs to workspace</label>
                  </div>
                </div>
              </div>
              <div class="card">
                <div class="card-header">
                  <h5>Outputs</h5>
                </div>
                <div class="card-body">
                  <div class="form-row ccp-outputs">
                    <div class="col form-group"></div>
                  </div>
                </div>
              </div>
              <div class="form-row">
                <div class="col-6">
                  <button id="execute_method_button" class="btn btn-info">Execute</button>
                </div>
                <div class="col-6">
                  <div class="mb-3">
                    <label>Generate code for:</label>
                    <div class="d-flex">
                      <select name="language-selector" class="form-control" style="padding:2px">
                        <option value="text/python" data-ext="py" title="Generate plain Python3">Python 3</option>
                        <option value="text/plain+r" data-ext="r" title="Generate plain R">R</option>
                        <option value="application/vnd.jupyter+python" data-ext="ipynb" title="Generate Jupyter notebook with Python 3 cells">Jupyter Python3</option>
                        <option value="text/julia" data-ext="jl" title="Generate Julia 1.9.0 code (HTTP 1.10.0, JSON3 1.13.2)">Julia 1.9.0</option>
                        <option value="application/x-sh+curl" data-ext="sh" title="Generate Bash script (curl)">Bash (curl)</option>
                        <option value="application/x-sh+wget" data-ext="sh" title="Generate Bash script (wget)">Bash (wget)</option>
                       </select>
                      <button name="codegen" title="Generate code" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                        <svg viewBox="0 96 960 960">
                          <path d="M320 814 80 574l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"></path>
                        </svg>
                      </button>
                    </div>
                  </div>
                  <div>
                    <div class="d-flex">
                      <a name="direct_link_method" class="text-truncate" href="${window.location.href}">Direct link</a>
                    </div>
                  </div>
                </div>
              </div>
            </form>
          </div>    
        </template>
        <template id="EXECUTION_FORM_EMPTY_TEMPLATE">
          <div name="execution_form">
            <i style="padding:3rem">Drop a method here!</i>
          </div>
        </template>
        <div name="execution_form"></div>
      </div>
    `
  }
  
  lockRender(){
    const plexi = this.#rootdoc.querySelector(".plexiglass")
    plexi.innerHTML = ``
    plexi.classList.toggle("d-none")
  }
  
  unlockRender(){
    this.#rootdoc.querySelector(".plexiglass").classList.toggle("d-none")
  }
  
  writeToPlexi(message){
    const plexi = this.#rootdoc.querySelector(".plexiglass")
    plexi.innerHTML = `
      <div class="d-flex" style="flex-direction: column;justify-content:center;position:relative;height:100%;align-items: center;">
        <div class="mx-2 p-4 bg-success text-white border" style="border-radius: 5px;box-shadow: 2px 2px 2px darkgray;">
            <div>${message}</h5></div>
        </div>
    </div>
    `
  }
  
  loadMethod(){
    return this.#boot.secureFetch(this.#serviceurl + "/processes/" + this.#method).then(
      (resp)=>{
        if(resp.status === 200){
          return resp.json()
        }else throw "Error retrieving process"
      }
    ).then(data=>{
      this.#data = data
      const infra = 
        this.#data.links.filter(l => l.rel === "compatibleWith")[0]
      return this.#boot.secureFetch(this.#serviceurl + "/" + infra.href)
      
    }).then(resp=>{
      this.#data.executable = resp.status === 200 
    }).then(()=>{
      this.showMethod()
    }).catch(err=>alert(err))
  }
  
  showEmpty(resp){
    BSS.apply(this.#empty_executionform_bss, this.#rootdoc)
  }
  
  showMethod(){
    if(this.#method == null) this.showEmpty();
    else{
      BSS.apply(this.#executionform_bss, this.#rootdoc)
    }
  }
  
  sendExecutionRequest(){
    this.lockRender()
    const url = this.#serviceurl + "/processes/" + this.#method + "/execution"
    const req = this.buildRequest()
    this.#boot.secureFetch(
      url, { method : "POST", body : JSON.stringify(req), headers : { "Content-Type" : "application/json"}}
    ).then(reply=>{
      if(reply.status !== 200 && reply.status !== 201){
        throw "Error while requesting resource"
      }
      return reply.json()
    }).then(data=>{
      if(data.status !== "accepted"){
        throw "Execution has not been accepted by server"
      }
      const event = new CustomEvent('newexecution', { detail: data.jobID });
      document.dispatchEvent(event)
      this.writeToPlexi("Execution request accepted with id: " + data.jobID)
      window.setTimeout(()=>this.unlockRender(), 3000)
    }).catch(err => {alert("Unable to call execute: " + err); this.unlockRender()})
  }
  
  generateCode(mime, filename){
    const url = this.#serviceurl + "/methods/" + this.#method + "/code"
    const req = this.buildRequest()
    this.#boot.secureFetch(
      url, { method : "POST", body : JSON.stringify(req), headers : { "Content-Type" : "application/json", "Accept" : mime}}
    ).then(reply=>{
      if(reply.status !== 200) throw "Error while requesting code:";
      return reply.blob()
    }).then(blob => {
      const objectURL = URL.createObjectURL(blob)
      var tmplnk = document.createElement("a")
      tmplnk.download = filename
      tmplnk.href = objectURL
      document.body.appendChild(tmplnk)
      tmplnk.click()
      document.body.removeChild(tmplnk)
    }).catch(err=>{ alert(err)})
  }
  
  buildRequest(){
    let request = { inputs : {}, outputs : {}, response : "raw"}
    
    //fill inputs
    const inputs = this.getInputs()
    inputs.forEach(i=>{
      request.inputs[i.name] = i.value
    })
    
    //fill outputs
    const outputs = this.getOutputs()
    outputs.forEach(o=>{
      if(o.enabled) request.outputs[o.name] = { transmissionMode : "value" };
    })

    const autoarchiveoption = this.#rootdoc.querySelector("input[name='auto-archive-outputs']")
    if (autoarchiveoption.checked){
      request.subscribers = [
        { successUri : "http://registry:8080/executions/archive-to-folder" }
      ]
    }
   
    return request
  }
  
  getInputs(){
    return Array.prototype.slice.call(this.#rootdoc.querySelectorAll("d4s-ccp-input"))
  }
  
  getOutputs(){
    return Array.prototype.slice.call(this.#rootdoc.querySelectorAll("d4s-ccp-output"))
  }
  
  initInputValues(inputs){
    Object.keys(inputs).forEach(k=>{ 
      const w = this.#rootdoc.querySelector(`d4s-ccp-input[name=${k}]`)
      if(w){
        w.value = (inputs[k])
      }
    })
  }

  initOptionValues(request){
    const autoarchiveoption = this.#rootdoc.querySelector("input[name='auto-archive-outputs']")
    autoarchiveoption.checked = request.subscribers && request.subscribers.filter(s=>s.successUri === "http://registry:8080/executions/archive-to-folder").length === 1
  }
  
  prepareFromExecution(exec){
    let f1 = 
      this.#boot.secureFetch(this.#serviceurl + `/executions/${exec.id}/metadata/method.json`)
      .then(resp=>{
        if(resp.status === 200){
          return resp.json()
        }else throw "Error retrieving process"
      }
    ).then(data=>data)
    
    let f2 = 
      this.#boot.secureFetch(this.#serviceurl + `/executions/${exec.id}/metadata/request.json`)
      .then(resp=>{
        if(resp.status === 200){
          return resp.json()
        }else throw "Error retrieving process"
      }
    ).then(data=>data)
  
    var requestdata = null
    Promise.all([f1, f2]).then(m_and_r => {
      this.#data = m_and_r[0]
      requestdata = m_and_r[1]
      this.#method = this.#data.id
      const infra = 
        this.#data.links.filter(l => l.rel === "compatibleWith")[0]
      return this.#boot.secureFetch(this.#serviceurl + "/" + infra.href)
    }).then(resp=>{
      this.#data.executable = resp.status === 200 
    }).then(()=>{
      this.showMethod()
      this.initInputValues(requestdata.inputs)  
      this.initOptionValues(requestdata)  
    }).catch(err=>alert(err))
  }
  
  #empty_executionform_bss = {
    template : "#EXECUTION_FORM_EMPTY_TEMPLATE",
    target : "div[name=execution_form]",
    on_drop : ev=>{
      if(ev.dataTransfer){
        if(ev.dataTransfer.getData('text/plain+ccpmethod')){
          const id = ev.dataTransfer.getData('text/plain+ccpmethod')
          this.setAttribute("method", id);
          ev.preventDefault()
          ev.stopPropagation()
          ev.currentTarget.style.backgroundColor = "white"
        }else if(ev.dataTransfer.getData('text/plain+ccpexecution')){
          this.prepareFromExecution(JSON.parse(ev.dataTransfer.getData('application/json+ccpexecution')))
          ev.preventDefault()
          ev.stopPropagation()
          ev.currentTarget.style.backgroundColor = "white"
        }
      }
    },
    on_dragover : ev=>{
      ev.preventDefault()
    },
  }
  
  #executionform_bss = {
    template : "#EXECUTION_FORM_TEMPLATE",
    target : "div[name=execution_form]",
    in : ()=>this.#data,
    on_drop : ev=>{
      if(ev.dataTransfer){
        if(ev.dataTransfer.getData('text/plain+ccpmethod')){
          const id = ev.dataTransfer.getData('text/plain+ccpmethod');
          this.setAttribute("method", id);
          ev.preventDefault()
          ev.stopPropagation()
        }else if(ev.dataTransfer.getData('text/plain+ccpexecution')){
          this.prepareFromExecution(JSON.parse(ev.dataTransfer.getData('application/json+ccpexecution')))
          ev.preventDefault()
          ev.stopPropagation()
          ev.currentTarget.style.backgroundColor = "white"
        }
      }
    },
    on_dragover : ev=>{
      ev.preventDefault()
    },
    recurse : [
      {
        target: ".ccp-method-title",
        apply : (e,d)=>e.innerHTML = `
          ${d.title} <span class="badge badge-primary ml-1">${d.version}</span>
          ${ d.metadata.filter(md=>md.role === 'author').map(a=>`<span class="badge badge-warning ml-1">${a.title}</span>`)}
        `
      },
      {
        target: "p.description",
        apply : (e,d)=>e.textContent = d.description
      },
      {
        target: "div.ccp-inputs",
        in : (e,d)=>d,
        recurse : [
          {
            "in" : (e,d)=>{ return Object.values(d.inputs) },
            target : "div",
            apply : (e,d)=>{
              e.innerHTML = `<d4s-ccp-input name="${d.id}" input='${btoa(JSON.stringify(d))}'></d4s-ccp-input>`
            }
          }
        ]
      },
      {
        target: "div.ccp-outputs",
        in : (e,d)=>d,
        recurse : [
          {
            "in" : (e,d)=>{ return Object.values(d.outputs) },
            target : "div",
            apply : (e,d)=>{
              e.innerHTML = `<d4s-ccp-output name="${d.id}" output='${btoa(JSON.stringify(d))}'></d4s-ccp-output>`
            }
          }
        ]
      },
      {
        target: "div.ccp-runtimes *[name=refresh-runtimes]",
        on_click : ev=>{
          BSS.apply(this.#executionform_bss)
        }
      },
      {
        target: "#execute_method_button",
        on_click : ev=>{
          ev.preventDefault()
          ev.stopPropagation()
          if(this.#data.executable) this.sendExecutionRequest();
          else alert("This method has no compatible runtimes available")
          return false;
        }
      },
      {
        target: "button[name=codegen]",
        on_click : ev=>{
          ev.preventDefault()
          ev.stopPropagation()
          const langsel = ev.target.parentElement.querySelector("select[name=language-selector]")
          const lang = langsel.value
          const ext = langsel.selectedOptions[0].getAttribute("data-ext")
          this.generateCode(lang, `ccprequest.${ext}`)
          return false
        }
      },
      {
        target: "a[name=direct_link_method]",
        apply : (e,d)=>e.href = window.location.origin + window.location.pathname + "?method=" + d.id
      }
    ]
  }
  
}

window.customElements.define('d4s-ccp-executionform', CCPExecutionForm);
