(function (global) {
  'use strict';

  var STUN = [{ urls: 'stun:stun.l.google.com:19302' }];

  function signalingUrl(basePath) {
    return basePath + '/api/live_cast_signaling.php';
  }

  function castApiUrl(basePath) {
    return basePath + '/api/live_cast.php';
  }

  function xhrJson(method, url, body, cb) {
    var xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    if (body != null) {
      xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
    }
    xhr.onload = function () {
      var payload = null;
      try {
        payload = JSON.parse(xhr.responseText);
      } catch (e) {
        cb(null);
        return;
      }
      cb(payload);
    };
    xhr.onerror = function () { cb(null); };
    xhr.send(body != null ? JSON.stringify(body) : null);
  }

  function postSignal(basePath, sessionId, peerRole, msgType, payload, cb) {
    xhrJson('POST', signalingUrl(basePath), {
      action: 'post',
      session_id: sessionId,
      peer_role: peerRole,
      msg_type: msgType,
      payload: payload
    }, cb);
  }

  function pollSignals(basePath, sessionId, sinceId, fromRole, cb) {
    var qs = '?action=poll&session_id=' + encodeURIComponent(sessionId)
      + '&since_id=' + encodeURIComponent(String(sinceId || 0));
    if (fromRole) {
      qs += '&from_role=' + encodeURIComponent(fromRole);
    }
    xhrJson('GET', signalingUrl(basePath) + qs, null, cb);
  }

  function collectStats(pc, videoEl) {
    var stats = {
      ice_state: pc ? pc.iceConnectionState : 'new',
      signaling_state: pc ? pc.signalingState : 'stable',
      fps: 0,
      inbound_kbps: 0,
      outbound_kbps: 0,
      rtt_ms: 0,
      frames_decoded: 0
    };
    if (videoEl && videoEl.videoWidth) {
      stats.frame_w = videoEl.videoWidth;
      stats.frame_h = videoEl.videoHeight;
    }
    return stats;
  }

  function enrichStatsFromRtc(pc, stats, cb) {
    if (!pc || !pc.getStats) {
      cb(stats);
      return;
    }
    pc.getStats().then(function (report) {
      report.forEach(function (r) {
        if (r.type === 'inbound-rtp' && r.kind === 'video') {
          stats.frames_decoded = r.framesDecoded || stats.frames_decoded;
          if (r.bytesReceived && r.timestamp) {
            stats.inbound_kbps = Math.round((r.bytesReceived * 8) / 1000);
          }
        }
        if (r.type === 'outbound-rtp' && r.kind === 'video') {
          if (r.bytesSent) {
            stats.outbound_kbps = Math.round((r.bytesSent * 8) / 1000);
          }
        }
        if (r.type === 'candidate-pair' && r.state === 'succeeded' && r.currentRoundTripTime) {
          stats.rtt_ms = Math.round(r.currentRoundTripTime * 1000);
        }
      });
      cb(stats);
    }).catch(function () { cb(stats); });
  }

  function sendHeartbeat(basePath, sessionId, status, stats, cb) {
    var body = { action: 'heartbeat', session_id: sessionId, status: status || 'active' };
    if (stats) body.stats = stats;
    xhrJson('POST', castApiUrl(basePath), body, cb || function () {});
  }

  /**
   * @param {object} opts
   * @param {string} opts.basePath
   * @param {string} opts.sessionId
   * @param {'caster'|'viewer'|'mobile'} opts.role
   * @param {MediaStream|null} opts.localStream
   * @param {function(MediaStream)} [opts.onRemoteStream]
   * @param {function(object)} [opts.onStats]
   * @param {function(string)} [opts.onStatus]
   */
  function connect(opts) {
    var basePath = opts.basePath || '';
    var sessionId = opts.sessionId || '';
    var role = opts.role || 'viewer';
    var localStream = opts.localStream || null;
    var onRemoteStream = opts.onRemoteStream || function () {};
    var onStats = opts.onStats || function () {};
    var onStatus = opts.onStatus || function () {};
    var remoteRole = role === 'caster' ? 'viewer' : 'caster';
    var sinceId = 0;
    var pc = new RTCPeerConnection({ iceServers: STUN });
    var pollTimer = null;
    var hbTimer = null;
    var remoteVideo = null;
    var stopped = false;

    function status(msg) {
      onStatus(msg);
    }

    function teardown() {
      stopped = true;
      if (pollTimer) clearInterval(pollTimer);
      if (hbTimer) clearInterval(hbTimer);
      pollTimer = null;
      hbTimer = null;
      try { pc.close(); } catch (e) { /* ignore */ }
    }

    pc.onicecandidate = function (ev) {
      if (!ev.candidate) return;
      postSignal(basePath, sessionId, role, 'candidate', {
        candidate: ev.candidate.candidate,
        sdpMid: ev.candidate.sdpMid,
        sdpMLineIndex: ev.candidate.sdpMLineIndex
      }, function () {});
    };

    pc.ontrack = function (ev) {
      var stream = ev.streams && ev.streams[0] ? ev.streams[0] : new MediaStream([ev.track]);
      onRemoteStream(stream);
    };

    pc.oniceconnectionstatechange = function () {
      status('ICE: ' + pc.iceConnectionState);
    };

    if (localStream) {
      localStream.getTracks().forEach(function (t) { pc.addTrack(t, localStream); });
    }

    function applyRemoteDescription(payload, cb) {
      if (!payload || !payload.type || !payload.sdp) {
        cb(false);
        return;
      }
      pc.setRemoteDescription(payload).then(function () {
        cb(true);
      }).catch(function () { cb(false); });
    }

    function handlePollMessages(messages) {
      if (!messages || !messages.length) return;
      messages.forEach(function (msg) {
        sinceId = Math.max(sinceId, msg.id || 0);
        var payload = msg.payload || {};
        if (msg.msg_type === 'offer' && role === 'viewer') {
          applyRemoteDescription(payload, function (ok) {
            if (!ok) return;
            pc.createAnswer().then(function (answer) {
              return pc.setLocalDescription(answer);
            }).then(function () {
              postSignal(basePath, sessionId, role, 'answer', pc.localDescription, function () {});
              status('Answer sent — connecting…');
            }).catch(function () {
              status('Could not create WebRTC answer.');
            });
          });
        } else if (msg.msg_type === 'answer' && role === 'caster') {
          applyRemoteDescription(payload, function (ok) {
            if (ok) status('Viewer connected — streaming.');
          });
        } else if (msg.msg_type === 'candidate' && payload.candidate) {
          pc.addIceCandidate({
            candidate: payload.candidate,
            sdpMid: payload.sdpMid,
            sdpMLineIndex: payload.sdpMLineIndex
          }).catch(function () {});
        }
      });
    }

    pollTimer = setInterval(function () {
      if (stopped) return;
      pollSignals(basePath, sessionId, sinceId, remoteRole, function (resp) {
        if (!resp || !resp.ok) return;
        handlePollMessages(resp.messages || []);
        if (typeof resp.since_id === 'number') {
          sinceId = Math.max(sinceId, resp.since_id);
        }
      });
    }, 800);

    hbTimer = setInterval(function () {
      if (stopped) return;
      enrichStatsFromRtc(pc, collectStats(pc, remoteVideo), function (stats) {
        onStats(stats);
        sendHeartbeat(basePath, sessionId, 'active', stats);
      });
    }, 5000);

    if (role === 'caster') {
      status('Publishing offer…');
      pc.createOffer().then(function (offer) {
        return pc.setLocalDescription(offer);
      }).then(function () {
        postSignal(basePath, sessionId, role, 'offer', pc.localDescription, function (resp) {
          if (resp && resp.ok) {
            status('Waiting for viewer…');
          } else {
            status('Signaling failed.');
          }
        });
      }).catch(function () {
        status('Could not create WebRTC offer.');
      });
    } else {
      status('Waiting for caster offer…');
    }

    return {
      setRemoteVideo: function (el) { remoteVideo = el; },
      stop: function () {
        sendHeartbeat(basePath, sessionId, 'ended', null);
        teardown();
      }
    };
  }

  global.LiveCastWebRtc = {
    connect: connect,
    sendHeartbeat: sendHeartbeat,
    collectStats: collectStats
  };
})(typeof window !== 'undefined' ? window : this);
