const options = {
  legend: {
    position: "ne",
    show: true,
    noColumns: 3,
  },
  zoom: {
    interactive: true
  },
  pan: {
    interactive: true,
    cursor: "move",
    frameRate: 60
  },
  series: {
    lines: {
      show: true,
      lineWidth: 2
    }
  },
  xaxis: {
    tickDecimals: 2,
    //tickSize: 0.01
  },
  yaxis: {
    tickDecimals: 2,
    //tickSize: 0.01
  }
}

window.onload = function() {
  const fileSelector = document.getElementById('file-selector')

  fileSelector.addEventListener('change', (event) => {
    const fileList = event.target.files
    importFiles(fileList)
  })

  const dropArea = document.getElementsByClassName('content')[0];

  dropArea.addEventListener('dragover', (event) => {
    event.stopPropagation()
    event.preventDefault()
    // Style the drag-and-drop as a "copy file" operation.
    event.dataTransfer.dropEffect = 'copy'
  });

  dropArea.addEventListener('drop', (event) => {
    event.stopPropagation()
    event.preventDefault()
    const fileList = event.dataTransfer.files
    importFiles(fileList)
  })

  const uploadBtn = document.getElementById("upload-btn");
  const responseP = document.getElementById("response");
  uploadBtn.addEventListener('click', (event) => {
    uploadBtn.disabled = true;
    responseP.textContent = "waiting for response..."
    const xmlhttp = new XMLHttpRequest()
    xmlhttp.open("POST", "markers/create", true);
    xmlhttp.setRequestHeader("Content-type", "application/json")
    const token = document.querySelector('[name=csrfmiddlewaretoken]').value
    xmlhttp.setRequestHeader('X-CSRFToken', token)
    xmlhttp.onreadystatechange = () => {
      if(xmlhttp.readyState == 4) {
        if(xmlhttp.status < 300) {
          responseP.textContent = "Markers created!"
          const body = JSON.parse(xmlhttp.responseText)
          for(const field in body)
            responseP.innerHTML += `<br/>${field}: ${body[field]}`
        }
        else
          responseP.textContent = `Error code ${xmlhttp.status}`
        uploadBtn.disabled = false
      }
    }
    xmlhttp.send(JSON.stringify(markersToUpload))
  })

  const censorBtn = document.getElementById("censor-btn")
  censorBtn.addEventListener('click', (event) => {
    censorBtn.disabled = true;
    responseP.textContent = "waiting for response..."
    const xmlhttp = new XMLHttpRequest()
    xmlhttp.open("GET", "markers/censor", true)
    xmlhttp.onreadystatechange = () => {
      if(xmlhttp.readyState == 4) {
        if(xmlhttp.status < 300) {
          responseP.textContent = "Markers censored!"
          const body = JSON.parse(xmlhttp.responseText)
          for(const field in body)
            responseP.innerHTML += `<br/>${field}: ${body[field]}`
        }
        else
          responseP.textContent = `Error code ${xmlhttp.status}`
          censorBtn.disabled = false
      }
    }
    xmlhttp.send()
  })

  const tripBtn = document.getElementById("trip-btn")
  tripBtn.addEventListener('click', (event) => {
    tripBtn.disabled = true;
    responseP.textContent = "waiting for response..."
    const xmlhttp = new XMLHttpRequest()
    xmlhttp.open("GET", "markers/create-trips", true)
    xmlhttp.onreadystatechange = () => {
      if(xmlhttp.readyState == 4) {
        if(xmlhttp.status < 300) {
          responseP.textContent = "Trips created!"
          const body = JSON.parse(xmlhttp.responseText)
          for(const field in body)
            responseP.innerHTML += `<br/>${field}: ${body[field]}`
        }
        else
          responseP.textContent = `Error code ${xmlhttp.status}`
          tripBtn.disabled = false
      }
    }
    xmlhttp.send()
  })
}
function importFiles(fileList) {
  d = []
  plot = $.plot("#placeholder", d, options);
  markersToUpload = {}
  for (const file of fileList) {
    extension = file.name?.toLowerCase().split('.').pop()
    if(extension === "txt")
      readTxtFile(file)
    else if (extension === "nmea")
      parseNmeaFile(file)
    else if (extension === "gpx")
      parseGpxFile(file)
    else
      console.log(`unsupported file type (${extension})`)
  }
  document.getElementById("upload-btn").style.display = "inline";
}

function readTxtFile(file) {
  const reader = new FileReader()
  reader.addEventListener('load', (event) => {
    const lines = event.target.result.split('\n')
    let result = []
    for(const line of lines) {
      const fields = line.split(',')
      if(fields.length != 8)
        continue
      
      result.push({
        timestamp: fields[0],
        lat: parseFloat(fields[1]),
        lng: parseFloat(fields[2]),
        alt: parseFloat(fields[3]),
        hdop: parseInt(fields[4]),
        speed: parseFloat(fields[5])
      })
    }
    process(file.name, result)
  });
  reader.readAsText(file)
}

function parseNmeaFile(file) {
  const reader = new FileReader()

  reader.addEventListener('load', (event) => {
    const lines = event.target.result.split('\n')
    let result = []
    let ggaObj, oldTime;
  
    for(const line of lines) {
      const obj = GPS.Parse(line);
      if (obj.type == "RMC") {
        if(ggaObj.time != oldTime) {
          oldTime = ggaObj.time
          result.push({
            timestamp: obj.time,
            lat: obj.lat,
            lng: obj.lon,
            alt: ggaObj.alt,
            hdop: ggaObj.hdop,
            speed: obj.speed
          })
        }
        ggaObj = null
      } else if(ggaObj) {
        if(ggaObj.time != oldTime) {
          oldTime = ggaObj.time
          result.push({
            timestamp: ggaObj.time,
            lat: ggaObj.lat,
            lng: ggaObj.lon,
            alt: ggaObj.alt,
            hdop: ggaObj.hdop,
            speed: null
          })
        }
        ggaObj = null
      } 
      if(obj.type == "GGA") {
        ggaObj = obj;
      }
    }
    process(file.name, result)
  });
  reader.readAsText(file)
}

function parseGpxFile(file) {
  const reader = new FileReader()

  reader.addEventListener('load', (event) => {
    let gpx = new gpxParser()
    gpx.parse(event.target.result)
    let result = []
    for (const track of gpx.tracks) {
      for (const point of track.points) {
        result.push({
          timestamp: point.time,
          lat: point.lat,
          lng: point.lon,
          alt: point.ele,
          hdop: null,
          speed: null
        })
      }
    }
    process(file.name, result)
  });
  reader.readAsText(file)
}

let markersToUpload;

function process(name, markers) {
  d.push({
    label: `${name} ${markers.length}`,
    data: markers.map(m => [m.lng, m.lat])
  })
  let valid = []
  for(let i=0; i<markers.length-2; i++) {
    const m1 = markers[i];
    const m2 = markers[i+1];
    const m3 = markers[i+2];
    const dx12 = distance(m1, m2)
    const dx23 = distance(m2, m3)
    const dx13 = distance(m2, m3)
    const dt12 = (m2.timestamp - m1.timestamp) / 1000
    const dt23 = (m3.timestamp - m2.timestamp) / 1000
    const dt13 = dt12 + dt23
    
    if(dt12 > 0 && dx12/dt12 > 50) {// > 50 m/s = 180 km/h
      console.log(`n=${i} ${m1.timestamp} too fast: ${dx12/dt12} m/s`)
      if(dx12/dt12 > dx13/dt13)
        continue
      else
        markers.pop(i+1)
    }
    else if(dt23 > 0 && dx23/dt23 > 50) {// > 50 m/s = 180 km/h
      console.log(`n=${i} ${m1.timestamp} too fast: ${dx23/dt23} m/s`)
      if(dx23/dt23 > dx13/dt13)
        markers.pop(i+1)
      else
        markers.pop(i+2)
    }
    else if(dx12 > 50 && dx12 > dx13) {
      console.log(`n=${i} ${m1.timestamp} too far: ${dx12} m`)
      continue
    }
    valid.push(m1)
  }

  markers = RamerDouglasPeucker2d(valid, 0.00001) //.0001 = 7m
  d.push({
    label: `RamerDouglasPeucker2d ${markers.length}`,
    data: markers.map(m => [m.lng, m.lat])
  })
  markers = clean(markers)
  d.push({
    label: `cleaned ${markers.length}`,
    data: markers.map(m => [m.lng, m.lat])
  })

  plot.setData(d)
  plot.setupGrid() //only necessary if your new data will change the axes or grid
  plot.draw()

  markersToUpload[name] = markers
}

function clean(markers) {
  let cleaned = [], tmp = [], oldHeading = 0

  const threshold = 5; // meter

  for(const marker of markers) {
    if(!tmp.length) {
      tmp.push(marker)
    }
    const heading = GPS.Heading(tmp[tmp.length-1].lat, tmp[tmp.length-1].lng, marker.lat, marker.lng)
    if(tmp.some(m => distance(m, marker) < threshold) && Math.abs(heading - oldHeading) > 90) {
      tmp.push(marker)
      oldHeading = heading
      continue
    }
    oldHeading = heading

    cleaned.push(marker)
    tmp = [marker]
  }
  return cleaned.concat(tmp.slice(1))
}

function distance(m1, m2) {
  const theta = m1.lng - m2.lng
  let dist = Math.sin(m1.lat * Math.PI / 180) * Math.sin(m2.lat * Math.PI / 180) + 
             Math.cos(m1.lat * Math.PI / 180) * Math.cos(m2.lat * Math.PI / 180) * Math.cos(theta * Math.PI / 180);
  dist = Math.acos(dist);
  dist = dist / Math.PI * 180;
  const miles = dist * 60 * 1.1515;

  return (miles * 1609.344); // Meter
}

function perpendicularDistance2d(ptX, ptY, l1x, l1y, l2x, l2y) {
  if (l2x == l1x)
  {
    //vertical lines - treat this case specially to avoid dividing
    //by zero
    return Math.abs(ptX - l2x);
  }
  else
  {
    const slope = ((l2y-l1y) / (l2x-l1x));
    const passThroughY = (0-l1x)*slope + l1y;
    return (Math.abs((slope * ptX) - ptY + passThroughY)) /
              (Math.sqrt(slope*slope + 1));
  }
}
function RamerDouglasPeucker2d(pointList, epsilon) {
  if (pointList.length < 2)
  {
    return pointList;
  }
  // Find the point with the maximum distance
  let dmax = 0;
  let index = 0;
  let totalPoints = pointList.length;
  for (let i = 1; i < (totalPoints - 1); i++)
  {
    let d = perpendicularDistance2d(
          pointList[i].lat, pointList[i].lng,
          pointList[0].lat, pointList[0].lng,
          pointList[totalPoints-1].lat,
          pointList[totalPoints-1].lng);
    if (d > dmax)
    {
      index = i;
      dmax = d;
    }
  }
  
  // If max distance is greater than epsilon, recursively simplify
  if (dmax >= epsilon)
  {
    // Recursive call on each 'half' of the polyline
    const recResults1 = RamerDouglasPeucker2d(pointList.slice(0, index + 1), epsilon);
    const recResults2 = RamerDouglasPeucker2d(pointList.slice(index), epsilon);
    // Build the result list
    return recResults1.slice(0, recResults1.length - 1).concat(recResults2);
  }
  else
  {
    return [pointList[0], pointList[totalPoints-1]];
  }
}