#!/usr/bin/ruby
#
# This file is part of centurio.work/ing/commands.
#
# centurio.work/ing/commands is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# centurio.work/ing/commands is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# centurio.work/ing/commands (file COPYING in the main directory). If not, see
# <http://www.gnu.org/licenses/>.

require 'rubygems'
require 'json'
require 'xml/smart'
require 'riddl/server'
require 'fileutils'
require 'typhoeus'

# process:
# https://centurio.work/customers/evva/flow/?monitor=https://centurio.work/flow-test/engine/729/


class NewInstance < Riddl::Implementation
  def response
  
    entries = Dir.entries(File.join('data')).size
    Dir.mkdir(File.join('data',entries.to_s)) rescue nil
    
    Riddl::Parameter::Complex.new('text','text/plain',entries.to_s)
  end
end

class Index < Riddl::Implementation
  def response
    Riddl::Parameter::Complex.new('url','text/html',File.open(File.join(__dir__,'template','index.html')))
  end
end

class Builder < Riddl::Implementation
  def response
    Riddl::Parameter::Complex.new('url','text/html',File.open(File.join(__dir__,'template','builder.html')))
  end
end

class DisplayForm < Riddl::Implementation
  def response
    #Riddl::Parameter::Complex.new('url','text/html',File.open(File.join('data',@r[0],'form_min.html')))
    Riddl::Parameter::Complex.new('url','text/html',File.open(File.join(__dir__,'template','form.html')))
  end
end

class SaveForm < Riddl::Implementation
  def response 
    File.write(File.join('data',@r[0],'form.json'),@p[0].value.read)
  end
end


class SaveHtmlForm < Riddl::Implementation
  def response 
    file = File.read(File.join(__dir__,'template','form_min.html'))
    File.write(File.join('data',@r[0],'form_min.html'),(file.sub! '<!--FormComesHere-->', @p[0].value.read))
  end
end


class GetJson < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-2],'form.json')
    if File.exists? fname
      Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
    else
      @status = 404
    end
  end
end #}}}






class Get < Riddl::Implementation
  def response
    if @r[0] == 'test'
      Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','test.html')))
    elsif @r[0] == 'menu'
      Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','menu.html')))
    else
      Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','template.html')))
    end
  end
end

class Put < Riddl::Implementation
  def response
    Dir.mkdir(File.join('data',@r.last)) rescue nil 
    File.write(File.join('data',@r.last,'style.url'),@p[0].value)
    
    File.write(File.join('data',@r.last,'document.xml'),@p[1].value)
    
    File.write(File.join('data',@r.last,'frames.json'),JSON.dump(JSON.parse('{"data":[]}')))
    
    
    File.write(File.join('data',@r.last,'info.json'),JSON.dump(JSON.parse('{"x_amount":' + @p[2].value + ', "y_amount":' + @p[3].value + ', "lang":"' + @p[4].value  + '", "langs":["' + @p[4].value +  '"]}')))


    File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])

    @a[0].send('new')
    nil
  end

  
  #def headers
  #  Riddl::Header.new('CPEE-CALLBACK', 'true')
  #end
end

class NewFrame < Riddl::Implementation
  def response    
    path = File.join('data',@r.last,'frames.json')
    file = File.read(path)
    data_hash = JSON::parse(file)
    
    data_hash["data"].each do | c |
      if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[0].value.to_i, @p[1].value.to_i, (@p[0].value.to_i + @p[2].value.to_i - 1), (@p[1].value.to_i + @p[3].value.to_i - 1))
        data_hash["data"].delete(c)
      end
    end
    
#    example
#    myObj = {
#      "lx":3,
#      "ly":3,
#      "rx":3,
#      "ry":3,
#      "url": {
#        "de-at":"xyz.at",
#        "en-us":"xyz.com"
#      }
#     }
    urls = JSON::parse(@p[4].value);
    
    hash = {lx: @p[0].value.to_i, ly: @p[1].value.to_i, rx: (@p[0].value.to_i + @p[2].value.to_i - 1), ry: (@p[1].value.to_i + @p[3].value.to_i - 1), url: urls};
    data_hash["data"].push(hash)
    File.write(path, JSON.dump(data_hash))
    
    #only send active url to client
    infofile = File.join('data',@r.last,'info.json')
    infojson = JSON::parse(File.read(infofile))
    hash["url"] = urls[infojson["lang"]]

    @a[0].send(JSON.dump(hash))
    nil
 
  end
end


class NewFramePut < Riddl::Implementation
  def response    
    path = File.join('data',@r.last,'frames.json')
    file = File.read(path)
    data_hash = JSON::parse(file)
    
    data_hash["data"].each do | c |
      if doOverlap(c['lx'], c['ly'], c['rx'], c['ry'], @p[0].value.to_i, @p[1].value.to_i, (@p[0].value.to_i + @p[2].value.to_i - 1), (@p[1].value.to_i + @p[3].value.to_i - 1))
        data_hash["data"].delete(c)
      end
    end
    
    urls = JSON::parse(@p[4].value);
    
    hash = {lx: @p[0].value.to_i, ly: @p[1].value.to_i, rx: (@p[0].value.to_i + @p[2].value.to_i - 1), ry: (@p[1].value.to_i + @p[3].value.to_i - 1), url: urls, callback: @h['CPEE_CALLBACK']};
    data_hash["data"].push(hash)
    File.write(path, JSON.dump(data_hash))
    
    #only send active url to client
    infofile = File.join('data',@r.last,'info.json')
    infojson = JSON::parse(File.read(infofile))
    hash["url"] = urls[infojson["lang"]]

    @a[0].send(JSON.dump(hash))
    nil
 
  end
  
  def headers
    Riddl::Header.new('CPEE-CALLBACK', 'true')
  end
end




def doOverlap(l1x, l1y, r1x, r1y, l2x, l2y, r2x, r2y) 
  if l1x > r2x || l2x > r1x
      return false;
  end
  if l1y > r2y || l2y > r1y
      return false;
  end
  return true; 
end


class Delete < Riddl::Implementation
  def response
    if cbu = File.read(File.join('data',@r.last,'callback'))
      send = { 'operation' => @p[0].value }
      case send['operation']
        when 'result'
          send['target'] = JSON::parse(@p[1].value.read)
      end
      cbu += '/' unless cbu[-1] == '/'

      Typhoeus.put(cbu, body: JSON::generate(send), headers: { 'content-type' => 'application/json'})
    end

    File.unlink(File.join('data',@r.last,'callback')) rescue nil
    File.unlink(File.join('data',@r.last,'style.url')) rescue nil
    File.unlink(File.join('data',@r.last,'document.xml')) rescue nil
    File.unlink(File.join('data',@r.last,'info.json')) rescue nil

    @a[0].send('reset')
    nil
  end
end

class GetFrames < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-2],'frames.json')
    if File.exists? fname
    
      infofile = File.join('data',@r[-2],'info.json')
      infojson = JSON::parse(File.read(infofile))
      
      #remove not used languages
      file = JSON::parse(File.read(fname))
      file["data"].each do |child|      
        child["url"] = child["url"][infojson["lang"]]
      end
      
      Riddl::Parameter::Complex.new('value','application/json',JSON.dump(file))
    else
      @status = 404
    end
  end
end #}}}



class GetLangs < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-2],'document.xml')
    if File.exists? fname
      doc = XML::Smart.open_unprotected(fname)
      ndoc = XML::Smart.string('<languages/>')
      doc.find('//@lang').each do |e|
        ndoc.root.add('language',e.value)
      end
      Riddl::Parameter::Complex.new('value','text/xml',ndoc.to_s)
    else
      @status = 404
    end
  end
end #}}}

class SetLang < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-2],'info.json')
    if File.exists? fname
      infojson = JSON::parse(File.read(fname))
      infojson["lang"] = @p[0].value
      
      
      #add to langs
      if !infojson["langs"].include?(@p[0].value)
        infojson["langs"].push(@p[0].value)
      end
      
      File.write(fname, JSON.dump(infojson))
      
      
      
      @a[0].send('reset')
      nil
    else
      @status = 404
    end
  end
end #}}}

class GetStyle < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-2],'style.url')
    if File.exists? fname
      Riddl::Parameter::Complex.new('url','text/plain',File.read(fname).strip)
    else
      @status = 404
    end
  end
end #}}}
class GetDocument < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-3],'document.xml')
    if File.exists? fname
      doc = XML::Smart.open_unprotected(fname)
      val = nil
      doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
        val = e.text
      end
      if val
        Riddl::Parameter::Complex.new('url','text/plain',val.strip)
      else
        @status = 404
      end
    else
      @status = 404
    end
  end
end #}}}
class GetButton < Riddl::Implementation #{{{
  def response
    fname = File.join('data',@r[-3],'document.xml')
    if File.exists? fname
      doc = XML::Smart.open_unprotected(fname)
      val = nil
      doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
        val = e.attributes['button']
      end
      if val
        Riddl::Parameter::Complex.new('url','text/plain',val.strip)
      else
        @status = 404
      end
    else
      @status = 404
    end
  end
end #}}}

class SSE < Riddl::SSEImplementation #{{{
  def onopen
    signals = @a[0]
    signals.add self
    send 'started'
  end

  def onclose
    signals = @a[0]
    signals.remove self
    nil
  end
end #}}}

class Signaling # {{{
  def initialize
    @binding = []
  end

  def add(binding)
    @binding << binding
  end
  def remove(binding)
    @binding.delete(binding)
  end
  def length
    @binding.length
  end

  def send(value)
    @binding.each do |b|
      b.send(value)
    end
  end
end #}}}

server = Riddl::Server.new(File.join(__dir__,'/forms.xml'), :host => 'localhost') do |opts|
  accessible_description true
  cross_site_xhr true

  opts[:signals] = {}

  parallel do
    loop do
      opts[:signals].each do |k,v|
        v.send('keepalive')
      end
      sleep 5
    end
  end

  on resource do
    run Index if get  
    run NewInstance if post  
  
    on resource do |r|
    
      run DisplayForm if get
      
      on resource 'builder' do
        run Builder if get
        run SaveForm if post 'form' 
        run SaveHtmlForm if post 'htmlform' 
      end
      on resource 'json' do
        run GetJson if get
      end
    
    
    
    
    
    
    
    
    
    
      idx = r[:r][0]
      opts[:signals][idx] ||= Signaling.new

      run Put, opts[:signals][idx] if put 'input'
      
      run NewFrame, opts[:signals][idx] if post 'frame'
      run NewFramePut, opts[:signals][idx] if put 'frame'
      run Delete, opts[:signals][idx] if delete 'opa'
      run Delete, opts[:signals][idx] if delete 'opb'
      on resource 'sse' do
        run SSE, opts[:signals][idx] if sse
      end
      on resource 'languages' do
        run GetLangs if get
        run SetLang, opts[:signals][idx] if post 'lang'
      end
      on resource 'style.url' do
        run GetStyle if get
      end
      on resource 'frames.json' do
        run GetFrames if get
      end
      
      on resource 'buttons' do
        on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
          run GetButton if get
        end
      end
      on resource 'documents' do
        on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
          run GetDocument if get
        end
      end
    end
  end
end.loop!