text:
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;

namespace media {

enum {
  kBufferFlagSyncFrame = 1,    // BUFFER_FLAG_SYNC_FRAME
  kBufferFlagEndOfStream = 4,  // BUFFER_FLAG_END_OF_STREAM
  kConfigureFlagEncode = 1,    // CONFIGURE_FLAG_ENCODE
};

static const std::string AudioCodecToAndroidMimeType(const AudioCodec& codec) {
  switch (codec) {
    case kCodecMP3:
      return "audio/mpeg";
    case kCodecVorbis:
      return "audio/vorbis";
    case kCodecAAC:
      return "audio/mp4a-latm";
    default:
      return std::string();
  }
}

static const std::string VideoCodecToAndroidMimeType(const VideoCodec& codec) {
  switch (codec) {
    case kCodecH264:
      return "video/avc";
    case kCodecVP8:
      return "video/x-vnd.on2.vp8";
    case kCodecVP9:
      return "video/x-vnd.on2.vp9";
    default:
      return std::string();
  }
}

static const std::string CodecTypeToAndroidMimeType(const std::string& codec) {
  // TODO(xhwang): Shall we handle more detailed strings like "mp4a.40.2"?
  if (codec == "avc1")
    return "video/avc";
  if (codec == "mp4a")
    return "audio/mp4a-latm";
  if (codec == "vp8" || codec == "vp8.0")
    return "video/x-vnd.on2.vp8";
  if (codec == "vp9" || codec == "vp9.0")
    return "video/x-vnd.on2.vp9";
  if (codec == "vorbis")
    return "audio/vorbis";
  return std::string();
}

// TODO(qinmin): using a map to help all the conversions in this class.
static const std::string AndroidMimeTypeToCodecType(const std::string& mime) {
  if (mime == "video/mp4v-es")
    return "mp4v";
  if (mime == "video/avc")
    return "avc1";
  if (mime == "video/x-vnd.on2.vp8")
    return "vp8";
  if (mime == "video/x-vnd.on2.vp9")
    return "vp9";
  if (mime == "audio/mp4a-latm")
    return "mp4a";
  if (mime == "audio/mpeg")
    return "mp3";
  if (mime == "audio/vorbis")
    return "vorbis";
  return std::string();
}

static ScopedJavaLocalRef<jintArray>
ToJavaIntArray(JNIEnv* env, scoped_ptr<jint[]> native_array, int size) {
  ScopedJavaLocalRef<jintArray> j_array(env, env->NewIntArray(size));
  env->SetIntArrayRegion(j_array.obj(), 0, size, native_array.get());
  return j_array;
}

// static
bool MediaCodecBridge::IsAvailable() {
  // MediaCodec is only available on JB and greater.
  if (base::android::BuildInfo::GetInstance()->sdk_int() < 16)
    return false;
  // Blacklist some devices on Jellybean as for MediaCodec support is buggy.
  // http://crbug.com/365494.
  if (base::android::BuildInfo::GetInstance()->sdk_int() == 16) {
    std::string model(base::android::BuildInfo::GetInstance()->model());
    return model != "GT-I9100" && model != "GT-I9300" && model != "GT-N7000";
  }
  return true;
}

// static
bool MediaCodecBridge::SupportsSetParameters() {
  // MediaCodec.setParameters() is only available starting with K.
  return base::android::BuildInfo::GetInstance()->sdk_int() >= 19;
}

// static
std::vector<MediaCodecBridge::CodecsInfo> MediaCodecBridge::GetCodecsInfo() {
  std::vector<CodecsInfo> codecs_info;
  if (!IsAvailable())
    return codecs_info;

  JNIEnv* env = AttachCurrentThread();
  std::string mime_type;
  ScopedJavaLocalRef<jobjectArray> j_codec_info_array =
      Java_MediaCodecBridge_getCodecsInfo(env);
  jsize len = env->GetArrayLength(j_codec_info_array.obj());
  for (jsize i = 0; i < len; ++i) {
    ScopedJavaLocalRef<jobject> j_info(
        env, env->GetObjectArrayElement(j_codec_info_array.obj(), i));
    ScopedJavaLocalRef<jstring> j_codec_type =
        Java_CodecInfo_codecType(env, j_info.obj());
    ConvertJavaStringToUTF8(env, j_codec_type.obj(), &mime_type);
    ScopedJavaLocalRef<jstring> j_codec_name =
        Java_CodecInfo_codecName(env, j_info.obj());
    CodecsInfo info;
    info.codecs = AndroidMimeTypeToCodecType(mime_type);
    ConvertJavaStringToUTF8(env, j_codec_name.obj(), &info.name);
    info.direction = static_cast<MediaCodecDirection>(
        Java_CodecInfo_direction(env, j_info.obj()));
    codecs_info.push_back(info);
  }
  return codecs_info;
}

// static
bool MediaCodecBridge::CanDecode(const std::string& codec, bool is_secure) {
  if (!IsAvailable())
    return false;

  JNIEnv* env = AttachCurrentThread();
  std::string mime = CodecTypeToAndroidMimeType(codec);
  if (mime.empty())
    return false;
  ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime);
  ScopedJavaLocalRef<jobject> j_media_codec_bridge =
      Java_MediaCodecBridge_create(env, j_mime.obj(), is_secure, false);
  if (!j_media_codec_bridge.is_null()) {
    Java_MediaCodecBridge_release(env, j_media_codec_bridge.obj());
    return true;
  }
  return false;
}

// static
bool MediaCodecBridge::IsKnownUnaccelerated(const std::string& mime_type,
                                            MediaCodecDirection direction) {
  if (!IsAvailable())
    return true;

  std::string codec_type = AndroidMimeTypeToCodecType(mime_type);
  std::vector<media::MediaCodecBridge::CodecsInfo> codecs_info =
      MediaCodecBridge::GetCodecsInfo();
  for (size_t i = 0; i < codecs_info.size(); ++i) {
    if (codecs_info[i].codecs == codec_type &&
        codecs_info[i].direction == direction) {
      // It would be nice if MediaCodecInfo externalized some notion of
      // HW-acceleration but it doesn't. Android Media guidance is that the
      // prefix below is always used for SW decoders, so that's what we use.
      if (!StartsWithASCII(codecs_info[i].name, "OMX.google.", true))
        return false;
    }
  }
  return true;
}

MediaCodecBridge::MediaCodecBridge(const std::string& mime,
                                   bool is_secure,
                                   MediaCodecDirection direction) {
  JNIEnv* env = AttachCurrentThread();
  CHECK(env);
  DCHECK(!mime.empty());
  ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime);
  j_media_codec_.Reset(
      Java_MediaCodecBridge_create(env, j_mime.obj(), is_secure, direction));
}

MediaCodecBridge::~MediaCodecBridge() {
  JNIEnv* env = AttachCurrentThread();
  CHECK(env);
  if (j_media_codec_.obj())
    Java_MediaCodecBridge_release(env, j_media_codec_.obj());
}

bool MediaCodecBridge::StartInternal() {
  JNIEnv* env = AttachCurrentThread();
  return Java_MediaCodecBridge_start(env, j_media_codec_.obj()) &&
         GetOutputBuffers();
}

MediaCodecStatus MediaCodecBridge::Reset() {
  JNIEnv* env = AttachCurrentThread();
  return static_cast<MediaCodecStatus>(
      Java_MediaCodecBridge_flush(env, j_media_codec_.obj()));
}

void MediaCodecBridge::Stop() {
  JNIEnv* env = AttachCurrentThread();
  Java_MediaCodecBridge_stop(env, j_media_codec_.obj());
}

void MediaCodecBridge::GetOutputFormat(int* width, int* height) {
  JNIEnv* env = AttachCurrentThread();

  *width = Java_MediaCodecBridge_getOutputWidth(env, j_media_codec_.obj());
  *height = Java_MediaCodecBridge_getOutputHeight(env, j_media_codec_.obj());
}

MediaCodecStatus MediaCodecBridge::QueueInputBuffer(
    int index,
    const uint8* data,
    size_t data_size,
    const base::TimeDelta& presentation_time) {
  DVLOG(3) << __PRETTY_FUNCTION__ << index << ": " << data_size;
  if (data_size > base::checked_cast<size_t>(kint32max))
    return MEDIA_CODEC_ERROR;
  if (data && !FillInputBuffer(index, data, data_size))
    return MEDIA_CODEC_ERROR;
  JNIEnv* env = AttachCurrentThread();
  return static_cast<MediaCodecStatus>(
      Java_MediaCodecBridge_queueInputBuffer(env,
                                             j_media_codec_.obj(),
                                             index,
                                             0,
                                             data_size,
                                             presentation_time.InMicroseconds(),
                                             0));
}

MediaCodecStatus MediaCodecBridge::QueueSecureInputBuffer(
    int index,
    const uint8* data,
    size_t data_size,
    const uint8* key_id,
    int key_id_size,
    const uint8* iv,
    int iv_size,
    const SubsampleEntry* subsamples,
    int subsamples_size,
    const base::TimeDelta& presentation_time) {
  DVLOG(3) << __PRETTY_FUNCTION__ << index << ": " << data_size;
  if (data_size > base::checked_cast<size_t>(kint32max))
    return MEDIA_CODEC_ERROR;
  if (data && !FillInputBuffer(index, data, data_size))
    return MEDIA_CODEC_ERROR;

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jbyteArray> j_key_id =
      base::android::ToJavaByteArray(env, key_id, key_id_size);
  ScopedJavaLocalRef<jbyteArray> j_iv =
      base::android::ToJavaByteArray(env, iv, iv_size);

  // MediaCodec.CryptoInfo documentations says passing NULL for |clear_array|
  // to indicate that all data is encrypted. But it doesn't specify what
  // |cypher_array| and |subsamples_size| should be in that case. Passing
  // one subsample here just to be on the safe side.
  int new_subsamples_size = subsamples_size == 0 ? 1 : subsamples_size;

  scoped_ptr<jint[]> native_clear_array(new jint[new_subsamples_size]);
  scoped_ptr<jint[]> native_cypher_array(new jint[new_subsamples_size]);

  if (subsamples_size == 0) {
    DCHECK(!subsamples);
    native_clear_array[0] = 0;
    native_cypher_array[0] = data_size;
  } else {
    DCHECK_GT(subsamples_size, 0);
    DCHECK(subsamples);
    for (int i = 0; i < subsamples_size; ++i) {
      DCHECK(subsamples[i].clear_bytes <= std::numeric_limits<uint16>::max());
      if (subsamples[i].cypher_bytes >
          static_cast<uint32>(std::numeric_limits<jint>::max())) {
        return MEDIA_CODEC_ERROR;
      }

      native_clear_array[i] = subsamples[i].clear_bytes;
      native_cypher_array[i] = subsamples[i].cypher_bytes;
    }
  }

  ScopedJavaLocalRef<jintArray> clear_array =
      ToJavaIntArray(env, native_clear_array.Pass(), new_subsamples_size);
  ScopedJavaLocalRef<jintArray> cypher_array =
      ToJavaIntArray(env, native_cypher_array.Pass(), new_subsamples_size);

  return static_cast<MediaCodecStatus>(
      Java_MediaCodecBridge_queueSecureInputBuffer(
          env,
          j_media_codec_.obj(),
          index,
          0,
          j_iv.obj(),
          j_key_id.obj(),
          clear_array.obj(),
          cypher_array.obj(),
          new_subsamples_size,
          presentation_time.InMicroseconds()));
}

void MediaCodecBridge::QueueEOS(int input_buffer_index) {
  DVLOG(3) << __PRETTY_FUNCTION__ << ": " << input_buffer_index;
  JNIEnv* env = AttachCurrentThread();
  Java_MediaCodecBridge_queueInputBuffer(env,
                                         j_media_codec_.obj(),
                                         input_buffer_index,
                                         0,
                                         0,
                                         0,
                                         kBufferFlagEndOfStream);
}

MediaCodecStatus MediaCodecBridge::DequeueInputBuffer(
    const base::TimeDelta& timeout,
    int* index) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> result = Java_MediaCodecBridge_dequeueInputBuffer(
      env, j_media_codec_.obj(), timeout.InMicroseconds());
  *index = Java_DequeueInputResult_index(env, result.obj());
  MediaCodecStatus status = static_cast<MediaCodecStatus>(
      Java_DequeueInputResult_status(env, result.obj()));
  DVLOG(3) << __PRETTY_FUNCTION__ << ": status: " << status
           << ", index: " << *index;
  return status;
}

MediaCodecStatus MediaCodecBridge::DequeueOutputBuffer(
    const base::TimeDelta& timeout,
    int* index,
    size_t* offset,
    size_t* size,
    base::TimeDelta* presentation_time,
    bool* end_of_stream,
    bool* key_frame) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> result =
      Java_MediaCodecBridge_dequeueOutputBuffer(
          env, j_media_codec_.obj(), timeout.InMicroseconds());
  *index = Java_DequeueOutputResult_index(env, result.obj());
  *offset = base::checked_cast<size_t>(
      Java_DequeueOutputResult_offset(env, result.obj()));
  *size = base::checked_cast<size_t>(
      Java_DequeueOutputResult_numBytes(env, result.obj()));
  if (presentation_time) {
    *presentation_time = base::TimeDelta::FromMicroseconds(
        Java_DequeueOutputResult_presentationTimeMicroseconds(env,
                                                              result.obj()));
  }
  int flags = Java_DequeueOutputResult_flags(env, result.obj());
  if (end_of_stream)
    *end_of_stream = flags & kBufferFlagEndOfStream;
  if (key_frame)
    *key_frame = flags & kBufferFlagSyncFrame;
  MediaCodecStatus status = static_cast<MediaCodecStatus>(
      Java_DequeueOutputResult_status(env, result.obj()));
  DVLOG(3) << __PRETTY_FUNCTION__ << ": status: " << status
           << ", index: " << *index << ", offset: " << *offset
           << ", size: " << *size << ", flags: " << flags;
  return status;
}

void MediaCodecBridge::ReleaseOutputBuffer(int index, bool render) {
  DVLOG(3) << __PRETTY_FUNCTION__ << ": " << index;
  JNIEnv* env = AttachCurrentThread();
  CHECK(env);

  Java_MediaCodecBridge_releaseOutputBuffer(
      env, j_media_codec_.obj(), index, render);
}

int MediaCodecBridge::GetInputBuffersCount() {
  JNIEnv* env = AttachCurrentThread();
  return Java_MediaCodecBridge_getInputBuffersCount(env, j_media_codec_.obj());
}

int MediaCodecBridge::GetOutputBuffersCount() {
  JNIEnv* env = AttachCurrentThread();
  return Java_MediaCodecBridge_getOutputBuffersCount(env, j_media_codec_.obj());
}

size_t MediaCodecBridge::GetOutputBuffersCapacity() {
  JNIEnv* env = AttachCurrentThread();
  return Java_MediaCodecBridge_getOutputBuffersCapacity(env,
                                                        j_media_codec_.obj());
}

bool MediaCodecBridge::GetOutputBuffers() {
  JNIEnv* env = AttachCurrentThread();
  return Java_MediaCodecBridge_getOutputBuffers(env, j_media_codec_.obj());
}

void MediaCodecBridge::GetInputBuffer(int input_buffer_index,
                                      uint8** data,
                                      size_t* capacity) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> j_buffer(Java_MediaCodecBridge_getInputBuffer(
      env, j_media_codec_.obj(), input_buffer_index));
  *data = static_cast<uint8*>(env->GetDirectBufferAddress(j_buffer.obj()));
  *capacity = base::checked_cast<size_t>(
      env->GetDirectBufferCapacity(j_buffer.obj()));
}

bool MediaCodecBridge::CopyFromOutputBuffer(int index,
                                            size_t offset,
                                            void* dst,
                                            int dst_size) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> j_buffer(
      Java_MediaCodecBridge_getOutputBuffer(env, j_media_codec_.obj(), index));
  void* src_data =
      reinterpret_cast<uint8*>(env->GetDirectBufferAddress(j_buffer.obj())) +
      offset;
  int src_capacity = env->GetDirectBufferCapacity(j_buffer.obj()) - offset;
  if (src_capacity < dst_size)
    return false;
  memcpy(dst, src_data, dst_size);
  return true;
}

bool MediaCodecBridge::FillInputBuffer(int index,
                                       const uint8* data,
                                       size_t size) {
  uint8* dst = NULL;
  size_t capacity = 0;
  GetInputBuffer(index, &dst, &capacity);
  CHECK(dst);

  if (size > capacity) {
    LOG(ERROR) << "Input buffer size " << size
               << " exceeds MediaCodec input buffer capacity: " << capacity;
    return false;
  }

  memcpy(dst, data, size);
  return true;
}

AudioCodecBridge::AudioCodecBridge(const std::string& mime)
    // Audio codec doesn't care about security level and there is no need for
    // audio encoding yet.
    : MediaCodecBridge(mime, false, MEDIA_CODEC_DECODER) {}

bool AudioCodecBridge::Start(const AudioCodec& codec,
                             int sample_rate,
                             int channel_count,
                             const uint8* extra_data,
                             size_t extra_data_size,
                             bool play_audio,
                             jobject media_crypto) {
  JNIEnv* env = AttachCurrentThread();

  if (!media_codec())
    return false;

  std::string codec_string = AudioCodecToAndroidMimeType(codec);
  if (codec_string.empty())
    return false;

  ScopedJavaLocalRef<jstring> j_mime =
      ConvertUTF8ToJavaString(env, codec_string);
  ScopedJavaLocalRef<jobject> j_format(Java_MediaCodecBridge_createAudioFormat(
      env, j_mime.obj(), sample_rate, channel_count));
  DCHECK(!j_format.is_null());

  if (!ConfigureMediaFormat(j_format.obj(), codec, extra_data, extra_data_size))
    return false;

  if (!Java_MediaCodecBridge_configureAudio(
           env, media_codec(), j_format.obj(), media_crypto, 0, play_audio)) {
    return false;
  }

  return StartInternal();
}

bool AudioCodecBridge::ConfigureMediaFormat(jobject j_format,
                                            const AudioCodec& codec,
                                            const uint8* extra_data,
                                            size_t extra_data_size) {
  if (extra_data_size == 0)
    return true;

  JNIEnv* env = AttachCurrentThread();
  switch (codec) {
    case kCodecVorbis: {
      if (extra_data[0] != 2) {
        LOG(ERROR) << "Invalid number of vorbis headers before the codec "
                   << "header: " << extra_data[0];
        return false;
      }

      size_t header_length[2];
      // |total_length| keeps track of the total number of bytes before the last
      // header.
      size_t total_length = 1;
      const uint8* current_pos = extra_data;
      // Calculate the length of the first 2 headers.
      for (int i = 0; i < 2; ++i) {
        header_length[i] = 0;
        while (total_length < extra_data_size) {
          size_t size = *(++current_pos);
          total_length += 1 + size;
          if (total_length > 0x80000000) {
            LOG(ERROR) << "Vorbis header size too large";
            return false;
          }
          header_length[i] += size;
          if (size < 0xFF)
            break;
        }
        if (total_length >= extra_data_size) {
          LOG(ERROR) << "Invalid vorbis header size in the extra data";
          return false;
        }
      }
      current_pos++;
      // The first header is identification header.
      ScopedJavaLocalRef<jbyteArray> first_header =
          base::android::ToJavaByteArray(env, current_pos, header_length[0]);
      Java_MediaCodecBridge_setCodecSpecificData(
          env, j_format, 0, first_header.obj());
      // The last header is codec header.
      ScopedJavaLocalRef<jbyteArray> last_header =
          base::android::ToJavaByteArray(
              env, extra_data + total_length, extra_data_size - total_length);
      Java_MediaCodecBridge_setCodecSpecificData(
          env, j_format, 1, last_header.obj());
      break;
    }
    case kCodecAAC: {
      media::BitReader reader(extra_data, extra_data_size);

      // The following code is copied from aac.cc
      // TODO(qinmin): refactor the code in aac.cc to make it more reusable.
      uint8 profile = 0;
      uint8 frequency_index = 0;
      uint8 channel_config = 0;
      if (!reader.ReadBits(5, &profile) ||
          !reader.ReadBits(4, &frequency_index)) {
        LOG(ERROR) << "Unable to parse AAC header";
        return false;
      }
      if (0xf == frequency_index && !reader.SkipBits(24)) {
        LOG(ERROR) << "Unable to parse AAC header";
        return false;
      }
      if (!reader.ReadBits(4, &channel_config)) {
        LOG(ERROR) << "Unable to parse AAC header";
        return false;
      }

      if (profile < 1 || profile > 4 || frequency_index == 0xf ||
          channel_config > 7) {
        LOG(ERROR) << "Invalid AAC header";
        return false;
      }
      const size_t kCsdLength = 2;
      uint8 csd[kCsdLength];
      csd[0] = profile << 3 | frequency_index >> 1;
      csd[1] = (frequency_index & 0x01) << 7 | channel_config << 3;
      ScopedJavaLocalRef<jbyteArray> byte_array =
          base::android::ToJavaByteArray(env, csd, kCsdLength);
      Java_MediaCodecBridge_setCodecSpecificData(
          env, j_format, 0, byte_array.obj());

      // TODO(qinmin): pass an extra variable to this function to determine
      // whether we need to call this.
      Java_MediaCodecBridge_setFrameHasADTSHeader(env, j_format);
      break;
    }
    default:
      LOG(ERROR) << "Invalid header encountered for codec: "
                 << AudioCodecToAndroidMimeType(codec);
      return false;
  }
  return true;
}

int64 AudioCodecBridge::PlayOutputBuffer(int index, size_t size) {
  DCHECK_LE(0, index);
  int numBytes = base::checked_cast<int>(size);
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> buf =
      Java_MediaCodecBridge_getOutputBuffer(env, media_codec(), index);
  uint8* buffer = static_cast<uint8*>(env->GetDirectBufferAddress(buf.obj()));

  ScopedJavaLocalRef<jbyteArray> byte_array =
      base::android::ToJavaByteArray(env, buffer, numBytes);
  return Java_MediaCodecBridge_playOutputBuffer(
      env, media_codec(), byte_array.obj());
}

void AudioCodecBridge::SetVolume(double volume) {
  JNIEnv* env = AttachCurrentThread();
  Java_MediaCodecBridge_setVolume(env, media_codec(), volume);
}

// static
AudioCodecBridge* AudioCodecBridge::Create(const AudioCodec& codec) {
  if (!MediaCodecBridge::IsAvailable())
    return NULL;

  const std::string mime = AudioCodecToAndroidMimeType(codec);
  return mime.empty() ? NULL : new AudioCodecBridge(mime);
}

// static
bool AudioCodecBridge::IsKnownUnaccelerated(const AudioCodec& codec) {
  return MediaCodecBridge::IsKnownUnaccelerated(
      AudioCodecToAndroidMimeType(codec), MEDIA_CODEC_DECODER);
}

// static
bool VideoCodecBridge::IsKnownUnaccelerated(const VideoCodec& codec,
                                            MediaCodecDirection direction) {
  return MediaCodecBridge::IsKnownUnaccelerated(
      VideoCodecToAndroidMimeType(codec), direction);
}

// static
VideoCodecBridge* VideoCodecBridge::CreateDecoder(const VideoCodec& codec,
                                                  bool is_secure,
                                                  const gfx::Size& size,
                                                  jobject surface,
                                                  jobject media_crypto) {
  if (!MediaCodecBridge::IsAvailable())
    return NULL;

  const std::string mime = VideoCodecToAndroidMimeType(codec);
  if (mime.empty())
    return NULL;

  scoped_ptr<VideoCodecBridge> bridge(
      new VideoCodecBridge(mime, is_secure, MEDIA_CODEC_DECODER));
  if (!bridge->media_codec())
    return NULL;

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime);
  ScopedJavaLocalRef<jobject> j_format(
      Java_MediaCodecBridge_createVideoDecoderFormat(
          env, j_mime.obj(), size.width(), size.height()));
  DCHECK(!j_format.is_null());
  if (!Java_MediaCodecBridge_configureVideo(env,
                                            bridge->media_codec(),
                                            j_format.obj(),
                                            surface,
                                            media_crypto,
                                            0)) {
    return NULL;
  }

  return bridge->StartInternal() ? bridge.release() : NULL;
}

// static
VideoCodecBridge* VideoCodecBridge::CreateEncoder(const VideoCodec& codec,
                                                  const gfx::Size& size,
                                                  int bit_rate,
                                                  int frame_rate,
                                                  int i_frame_interval,
                                                  int color_format) {
  if (!MediaCodecBridge::IsAvailable())
    return NULL;

  const std::string mime = VideoCodecToAndroidMimeType(codec);
  if (mime.empty())
    return NULL;

  scoped_ptr<VideoCodecBridge> bridge(
      new VideoCodecBridge(mime, false, MEDIA_CODEC_ENCODER));
  if (!bridge->media_codec())
    return NULL;

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime);
  ScopedJavaLocalRef<jobject> j_format(
      Java_MediaCodecBridge_createVideoEncoderFormat(env,
                                                     j_mime.obj(),
                                                     size.width(),
                                                     size.height(),
                                                     bit_rate,
                                                     frame_rate,
                                                     i_frame_interval,
                                                     color_format));
  DCHECK(!j_format.is_null());
  if (!Java_MediaCodecBridge_configureVideo(env,
                                            bridge->media_codec(),
                                            j_format.obj(),
                                            NULL,
                                            NULL,
                                            kConfigureFlagEncode)) {
    return NULL;
  }

  return bridge->StartInternal() ? bridge.release() : NULL;
}

VideoCodecBridge::VideoCodecBridge(const std::string& mime,
                                   bool is_secure,
                                   MediaCodecDirection direction)
    : MediaCodecBridge(mime, is_secure, direction),
      adaptive_playback_supported_for_testing_(-1) {}

void VideoCodecBridge::SetVideoBitrate(int bps) {
  JNIEnv* env = AttachCurrentThread();
  Java_MediaCodecBridge_setVideoBitrate(env, media_codec(), bps);
}

void VideoCodecBridge::RequestKeyFrameSoon() {
  JNIEnv* env = AttachCurrentThread();
  Java_MediaCodecBridge_requestKeyFrameSoon(env, media_codec());
}

bool VideoCodecBridge::IsAdaptivePlaybackSupported(int width, int height) {
  if (adaptive_playback_supported_for_testing_ == 0)
    return false;
  else if (adaptive_playback_supported_for_testing_ > 0)
    return true;
  JNIEnv* env = AttachCurrentThread();
  return Java_MediaCodecBridge_isAdaptivePlaybackSupported(
      env, media_codec(), width, height);
}

bool MediaCodecBridge::RegisterMediaCodecBridge(JNIEnv* env) {
  return RegisterNativesImpl(env);
}

}  // namespace media


text:
#ifndef __HTTP2_H
#define __HTTP2_H

#include "TCPIP.h"

#if defined(STACK_USE_HTTP2_SERVER)

/****************************************************************************
  Section:
	Server Configuration Settings
  ***************************************************************************/

  	#define HTTP_PORT               (80u)	// Listening port for HTTP server
	#define HTTPS_PORT				(443u)	// Listening port for HTTPS server (when SSL enabled)
	#define HTTP_MAX_DATA_LEN		(100u)	// Max bytes to store get and cookie args
	#define HTTP_MIN_CALLBACK_FREE	(16u)	// Min bytes free in TX FIFO before callbacks execute
	#define HTTP_CACHE_LEN			("600")	// Max lifetime (sec) of static responses as string
	#define HTTP_TIMEOUT			(45u)	// Max time (sec) to await more data before

	// Authentication requires Base64 decoding
	#if defined(HTTP_USE_AUTHENTICATION)
		#ifndef STACK_USE_BASE64_DECODE
			#define STACK_USE_BASE64_DECODE
		#endif
	#endif

/****************************************************************************
  Section:
	Commands and Server Responses
  ***************************************************************************/

	//Supported Commands and Server Response Codes
	typedef enum
	{
	    HTTP_GET = 0u,					// GET command is being processed
	    HTTP_POST,						// POST command is being processed
	    HTTP_BAD_REQUEST,				// 400 Bad Request will be returned
		HTTP_UNAUTHORIZED,				// 401 Unauthorized will be returned
	    HTTP_NOT_FOUND,					// 404 Not Found will be returned
		HTTP_OVERFLOW,					// 414 Request-URI Too Long will be returned
		HTTP_INTERNAL_SERVER_ERROR,		// 500 Internal Server Error will be returned
		HTTP_NOT_IMPLEMENTED,			// 501 Not Implemented (not a GET or POST command)
		#if defined(HTTP_MPFS_UPLOAD)
		HTTP_MPFS_FORM,					// Show the MPFS Upload form
		HTTP_MPFS_UP,					// An MPFS Upload is being processed
		HTTP_MPFS_OK,					// An MPFS Upload was successful
		HTTP_MPFS_ERROR,				// An MPFS Upload was not a valid image
		#endif
		HTTP_REDIRECT,					// 302 Redirect will be returned
		HTTP_SSL_REQUIRED				// 403 Forbidden is returned, indicating SSL is required
	} HTTP_STATUS;

/****************************************************************************
  Section:
	HTTP State Definitions
  ***************************************************************************/

	// Basic HTTP Connection State Machine
	typedef enum
	{
		SM_HTTP_IDLE = 0u,				// Socket is idle
		SM_HTTP_PARSE_REQUEST,			// Parses the first line for a file name and GET args
		SM_HTTP_PARSE_HEADERS,			// Reads and parses headers one at a time
		SM_HTTP_AUTHENTICATE,			// Validates the current authorization state
		SM_HTTP_PROCESS_GET,			// Invokes user callback for GET args or cookies
		SM_HTTP_PROCESS_POST,			// Invokes user callback for POSTed data
		SM_HTTP_PROCESS_REQUEST,		// Begins the process of returning data
		SM_HTTP_SERVE_HEADERS,			// Sends any required headers for the response
		SM_HTTP_SERVE_COOKIES,			// Adds any cookies to the response
		SM_HTTP_SERVE_BODY,				// Serves the actual content
		SM_HTTP_SEND_FROM_CALLBACK,		// Invokes a dynamic variable callback
		SM_HTTP_DISCONNECT				// Disconnects the server and closes all files
	} SM_HTTP2;

	// Result states for execution callbacks
	typedef enum
	{
		HTTP_IO_DONE = 0u,	// Finished with procedure
		HTTP_IO_NEED_DATA,	// More data needed to continue, call again later
		HTTP_IO_WAITING		// Waiting for asynchronous process to complete, call again later
	} HTTP_IO_RESULT;

	// Result states for HTTPPostReadName and HTTPPostReadValue
	typedef enum
	{
		HTTP_READ_OK = 0u,		// Read was successful
		HTTP_READ_TRUNCATED,	// Buffer overflow prevented by truncating value
		HTTP_READ_INCOMPLETE	// Entire object is not yet in the buffer.  Try again later.
	} HTTP_READ_STATUS;

	// File type definitions
	typedef enum
	{
		HTTP_TXT = 0u,		// File is a text document
		HTTP_HTM,			// File is HTML (extension .htm)
		HTTP_HTML,			// File is HTML (extension .html)
		HTTP_CGI,			// File is HTML (extension .cgi)
		HTTP_XML,			// File is XML (extension .xml)
		HTTP_CSS,			// File is stylesheet (extension .css)
		HTTP_GIF,			// File is GIF image (extension .gif)
		HTTP_PNG,			// File is PNG image (extension .png)
		HTTP_JPG,			// File is JPG image (extension .jpg)
		HTTP_JAVA,			// File is java (extension .java)
		HTTP_WAV,			// File is audio (extension .wav)
		HTTP_UNKNOWN		// File type is unknown
	} HTTP_FILE_TYPE;

	// HTTP Connection Struct
	// Stores partial state data for each connection
	// Meant for storage in fast access RAM
	typedef struct
	{
	    SM_HTTP2 sm;						// Current connection state
	    TCP_SOCKET socket;					// Socket being served
	} HTTP_STUB;

	#define sktHTTP		httpStubs[curHTTPID].socket		// Access the current socket
	#define smHTTP		httpStubs[curHTTPID].sm			// Access the current state machine

	// Stores extended state data for each connection
	typedef struct
	{
		DWORD byteCount;					// How many bytes have been read so far
		DWORD nextCallback;					// Byte index of the next callback
		DWORD callbackID;					// Callback ID to execute, also used as watchdog timer
		DWORD callbackPos;					// Callback position indicator
		BYTE *ptrData;						// Points to first free byte in data
		BYTE *ptrRead;						// Points to current read location
		MPFS_HANDLE file;					// File pointer for the file being served
	    MPFS_HANDLE offsets;				// File pointer for any offset info being used
		BYTE hasArgs;						// True if there were get or cookie arguments
		BYTE isAuthorized;					// 0x00-0x79 on fail, 0x80-0xff on pass
		HTTP_STATUS httpStatus;				// Request method/status
	    HTTP_FILE_TYPE fileType;			// File type to return with Content-Type
	  //		BYTE data[ HTTP_MAX_DATA_LEN ];		// General purpose data buffer
		#if defined(HTTP_USE_POST)
		BYTE smPost;						// POST state machine variable
		#endif
	} HTTP_CONN;

	#define RESERVED_HTTP_MEMORY ( (DWORD)MAX_HTTP_CONNECTIONS * (DWORD)sizeof(HTTP_CONN))

/****************************************************************************
  Section:
	Global HTTP Variables
  ***************************************************************************/

extern HTTP_CONN curHTTP;
extern HTTP_STUB httpStubs[MAX_HTTP_CONNECTIONS];
extern BYTE curHTTPID;

/****************************************************************************
  Section:
	Function Prototypes
  ***************************************************************************/

void HTTPInit(void);
void HTTPServer(void);
BYTE* HTTPURLDecode(BYTE* cData);
BYTE* HTTPGetArg(BYTE* cData, BYTE* cArg);
//ML void HTTPIncFile(ROM BYTE* cFile);

#if defined(__18CXX)
//ML	BYTE* HTTPGetROMArg(BYTE* cData, ROM BYTE* cArg);
#else
	// Non-ROM variant for C30 / C32
	#define HTTPGetROMArg(a,b)	HTTPGetArg(a,(BYTE*)b)
#endif

#if defined(HTTP_USE_POST)
	HTTP_READ_STATUS HTTPReadPostName(BYTE* cData, WORD wLen);
	HTTP_READ_STATUS HTTPReadPostValue(BYTE* cData, WORD wLen);
#endif

/*****************************************************************************
  Function:
	HTTP_READ_STATUS HTTPReadPostPair(BYTE* cData, WORD wLen)

  Summary:
	Reads a name and value pair from a URL encoded string in the TCP buffer.

  Description:
	Reads a name and value pair from a URL encoded string in the TCP buffer.
	This function is meant to be called from an HTTPExecutePost callback to
	facilitate easier parsing of incoming data.  This function also prevents
	buffer overflows by forcing the programmer to indicate how many bytes are
	expected.  At least 2 extra bytes are needed in cData over the maximum
	length of data expected to be read.

	This function will read until the next '&' character, which indicates the
	end of a value parameter.  It assumes that the front of the buffer is
	the beginning of the name paramter to be read.

	This function properly updates curHTTP.byteCount by decrementing it
	by the number of bytes read.  It also removes the delimiting '&' from
	the buffer.

	Once complete, two strings will exist in the cData buffer.  The first is
	the parameter name that was read, while the second is the associated
	value.

  Precondition:
	Front of TCP buffer is the beginning of a name parameter, and the rest of
	the TCP buffer contains a URL-encoded string with a name parameter
	terminated by a '=' character and a value parameter terminated by a '&'.

  Parameters:
	cData - where to store the name and value strings once they are read
	wLen - how many bytes can be written to cData

  Return Values:
	HTTP_READ_OK - name and value were successfully read
	HTTP_READ_TRUNCTATED - entire name and value could not fit in the buffer,
							so input was truncated and data has been lost
	HTTP_READ_INCOMPLETE - entire name and value was not yet in the buffer,
							so call this function again later to retrieve

  Remarks:
	This function is aliased to HTTPReadPostValue, since they effectively
	perform the same task.  The name is provided only for completeness.
  ***************************************************************************/
#define HTTPReadPostPair(cData, wLen) HTTPReadPostValue(cData, wLen)

/****************************************************************************
  Section:
	User-Implemented Callback Function Prototypes
  ***************************************************************************/

/*****************************************************************************
  Function:
	HTTP_IO_RESULT HTTPExecuteGet(void)

  Summary:
	Processes GET form field variables and cookies.

  Description:
	This function is implemented by the application developer in
	CustomHTTPApp.c.  Its purpose is to parse the data received from
	URL parameters (GET method forms) and cookies and perform any
	application-specific tasks in response to these inputs.  Any
	required authentication has already been validated.

	When this function is called, curHTTP.data contains sequential
	name/value pairs of strings representing the data received.  In this
	format, HTTPGetArg and HTTPGetROMArg can be used to search for
	specific variables in the input.  If data buffer space associated
	with this connection is required, curHTTP.data may be overwritten
	here once the application is done with the values.  Any data placed
	there will be available to future callbacks for this connection,
	including HTTPExecutePost and any HTTPPrint_varname dynamic
	substitutions.

	This function may also issue redirections by setting curHTTP.data
	to the destination file name or URL, and curHTTP.httpStatus to
	HTTP_REDIRECT.

	Finally, this function may set cookies.  Set curHTTP.data to a series
	of name/value string pairs (in the same format in which parameters
	arrive) and then set curHTTP.hasArgs equal to the number of cookie
	name/value pairs.  The cookies will be transmitted to the browser,
	and any future requests will have those values available in
	curHTTP.data.

  Precondition:
	None

  Parameters:
	None

  Return Values:
	HTTP_IO_DONE - application is done processing
	HTTP_IO_NEED_DATA - this value may not be returned because more data
						will not become available
	HTTP_IO_WAITING - the application is waiting for an asynchronous
					  process to complete, and this function should be
					  called again later

  Remarks:
	This function is only called if variables are received via URL
	parameters or Cookie arguments.  This function may NOT write to the
	TCP buffer.

	This function may service multiple HTTP requests simultaneously.
	Exercise caution when using global or static variables inside this
	routine.  Use curHTTP.callbackPos or curHTTP.data for storage associated
	with individual requests.
  ***************************************************************************/
HTTP_IO_RESULT HTTPExecuteGet(void);

/*****************************************************************************
  Function:
	HTTP_IO_RESULT HTTPExecutePost(void)

  Summary:
	Processes POST form variables and data.

  Description:
	This function is implemented by the application developer in
	CustomHTTPApp.c.  Its purpose is to parse the data received from
	POST forms and perform any application-specific tasks in response
	to these inputs.  Any required authentication has already been
	validated before this function is called.

	When this function is called, POST data will be waiting in the
	TCP buffer.  curHTTP.byteCount will indicate the number of bytes
	remaining to be received before the browser request is complete.

	Since data is still in the TCP buffer, the application must call
	TCPGet or TCPGetArray in order to retrieve bytes.  When this is done,
	curHTTP.byteCount MUST be updated to reflect how many bytes now
	remain.  The functions TCPFind, TCPFindString, TCPFindROMString,
	TCPFindArray, and TCPFindROMArray may be helpful to locate data
	in the TCP buffer.

	In general, data submitted from web forms via POST is URL encoded.
	The HTTPURLDecode function can be used to decode this information
	back to a standard string if required.  If data buffer space
	associated with this connection is required, curHTTP.data may be
	overwritten here once the application is done with the values.
	Any data placed there will be available to future callbacks for
	this connection,  including HTTPExecutePost and any
	HTTPPrint_varname dynamic substitutions.

	Whenever a POST form is processed it is recommended to issue a
	redirect back to the browser, either to a status page or to
	the same form page that was posted.  This prevents accidental
	duplicate submissions (by clicking refresh or back/forward) and
	avoids browser warnings about "resubmitting form data".  Redirects
	may be issued to the browser by setting curHTTP.data to the
	destination file or URL, and curHTTP.httpStatus to HTTP_REDIRECT.

	Finally, this function may set cookies.  Set curHTTP.data to a series
	of name/value string pairs (in the same format in which parameters
	arrive) and then set curHTTP.hasArgs equal to the number of cookie
	name/value pairs.  The cookies will be transmitted to the browser,
	and any future requests will have those values available in
	curHTTP.data.

  Precondition:
	None

  Parameters:
	None

  Return Values:
	HTTP_IO_DONE - application is done processing
	HTTP_IO_NEED_DATA - more data is needed to continue, and this
						function should be called again later
	HTTP_IO_WAITING - the application is waiting for an asynchronous
					  process to complete, and this function should
					  be called again later

  Remarks:
	This function is only called when the request method is POST, and is
	only used when HTTP_USE_POST is defined.  This method may NOT write
	to the TCP buffer.

	This function may service multiple HTTP requests simultaneously.
	Exercise caution when using global or static variables inside this
	routine.  Use curHTTP.callbackPos or curHTTP.data for storage associated
	with individual requests.
  ***************************************************************************/
#if defined(HTTP_USE_POST)
HTTP_IO_RESULT HTTPExecutePost(void);
#endif

/*****************************************************************************
  Function:
	BYTE HTTPNeedsAuth(BYTE* cFile)

  Summary:
	Determines if a given file name requires authentication

  Description:
	This function is implemented by the application developer in
	CustomHTTPApp.c.  Its function is to determine if a file being
	requested requires authentication to view.  The user name and password,
	if supplied, will arrive later with the request headers, and will be
	processed at that time.

	Return values 0x80 - 0xff indicate that authentication is not required,
	while values from 0x00 to 0x79 indicate that a user name and password
	are required before proceeding.  While most applications will only use a
	single value to grant access and another to require authorization, the
	range allows multiple "realms" or sets of pages to be protected, with
	different credential requirements for each.

	The return value of this function is saved as curHTTP.isAuthorized, and
	will be available to future callbacks, including HTTPCheckAuth and any
	of the HTTPExecuteGet, HTTPExecutePost, or HTTPPrint_varname callbacks.

  Precondition:
	None

  Parameters:
	cFile - the name of the file being requested

  Return Values:
	<= 0x79 - valid authentication is required
	>= 0x80 - access is granted for this connection

  Remarks:
	This function may NOT write to the TCP buffer.
  ***************************************************************************/
#if defined(HTTP_USE_AUTHENTICATION)
	BYTE HTTPNeedsAuth(BYTE* cFile);
#endif

/*****************************************************************************
  Function:
	BYTE HTTPCheckAuth(BYTE* cUser, BYTE* cPass)

  Summary:
	Performs validation on a specific user name and password.

  Description:
	This function is implemented by the application developer in
	CustomHTTPApp.c.  Its function is to determine if the user name and
	password supplied by the client are acceptable for this resource.

	The value of curHTTP.isAuthorized will be set to the previous return
	value of HTTPRequiresAuthorization.  This callback function can check
	this value to determine if only specific user names or passwords will
	be accepted for this resource.

	Return values 0x80 - 0xff indicate that the credentials were accepted,
	while values from 0x00 to 0x79 indicate that authorization failed.
	While most applications will only use a single value to grant access,
	flexibility is provided to store multiple values in order to
	indicate which user (or user's group) logged in.

	The return value of this function is saved as curHTTP.isAuthorized, and
	will be available to future callbacks, including any of the
	HTTPExecuteGet, HTTPExecutePost, or HTTPPrint_varname callbacks.

  Precondition:
	None

  Parameters:
	cUser - the user name supplied by the client
	cPass - the password supplied by the client

  Return Values:
	<= 0x79 - the credentials were rejected
	>= 0x80 - access is granted for this connection

  Remarks:
	This function is only called when an Authorization header is
	encountered.

	This function may NOT write to the TCP buffer.
  ***************************************************************************/
#if defined(HTTP_USE_AUTHENTICATION)
	BYTE HTTPCheckAuth(BYTE* cUser, BYTE* cPass);
#endif

/*****************************************************************************
  Function:
	void HTTPPrint_varname(void)
	void HTTPPrint_varname(WORD wParam1)
	void HTTPPrint_varname(WORD wParam1, WORD wParam2, ...)

  Summary:
	Inserts dynamic content into a web page

  Description:
	Functions in this style are implemented by the application developer in
	CustomHTTPApp.c.  These functions generate dynamic content to be
	inserted into web pages and other files returned by the HTTP2 server.

	Functions of this type are called when a dynamic variable is located
	in a web page.  (ie, ~varname~ )  The name between the tilde '~'
	characters is appended to the base function name.  In this example, the
	callback would be named HTTPPrint_varname.

	The function prototype is located in your project's HTTPPrint.h, which
	is automatically generated by the MPFS2 Utility.  The prototype will
	have WORD parameters included for each parameter passed in the dynamic
	variable.  For example, the variable "~myArray(2,6)~" will generate the
	prototype "void HTTPPrint_varname(WORD, WORD);".

	When called, this function should write its output directly to the TCP
	socket using any combination of TCPIsPutReady, TCPPut, TCPPutArray,
	TCPPutString, TCPPutROMArray, and TCPPutROMString.

	Before calling, the HTTP2 server guarantees that at least
	HTTP_MIN_CALLBACK_FREE bytes (defaults to 16 bytes) are free in the
	output buffer.  If the function is writing less than this amount, it
	should simply write the data to the socket and return.

	In situations where a function needs to write more this amount, it
	must manage its output state using curHTTP.callbackPos.  This value
	will be set to zero before the function is called.  If the function is
	managing its output state, it must set this to a non-zero value before
	returning.  Typically this is used to track how many bytes have been
	written, or how many remain to be written.  If curHTTP.callbackPos is
	non-zero, the function will be called again when more buffer space is
	available.  Once the callback completes, set this value back to zero
	to resume normal servicing of the request.

  Precondition:
	None

  Parameters:
	wParam1 - first parameter passed in the dynamic variable (if any)
	wParam2 - second parameter passed in the dynamic variable (if any)
	... - additional parameters as necessary

  Returns:
  	None

  Remarks:
	This function may service multiple HTTP requests simultaneously,
	especially when managing its output state.  Exercise caution when using
	global or static variables inside this routine.  Use curHTTP.callbackPos
	or curHTTP.data for storage associated with individual requests.
  ***************************************************************************/
#if defined(DOCUMENTATION_ONLY)
	void HTTPPrint_varname(WORD wParam1, WORD wParam2, ...);
#endif

#endif
#endif

redpajama_set_name: RedPajamaGithub


text:
ACCEPTED

#### According to
Index Fungorum

#### Published in
null

#### Original name
Limacinula melioloides var. eugeniicola Speg.

### Remarks
null


text:
<?php

/*
 * This file is part of Sulu.
 *
 * (c) Sulu GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\ArticleBundle\Document\Subscriber;

use Doctrine\ORM\EntityManagerInterface;
use PHPCR\ItemNotFoundException;
use PHPCR\SessionInterface;
use Sulu\Bundle\ArticleBundle\Document\Behavior\RoutableBehavior;
use Sulu\Bundle\ArticleBundle\Document\Behavior\RoutablePageBehavior;
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
use Sulu\Bundle\DocumentManagerBundle\Bridge\PropertyEncoder;
use Sulu\Bundle\RouteBundle\Entity\RouteRepositoryInterface;
use Sulu\Bundle\RouteBundle\Generator\ChainRouteGeneratorInterface;
use Sulu\Bundle\RouteBundle\Manager\ConflictResolverInterface;
use Sulu\Bundle\RouteBundle\Manager\RouteManagerInterface;
use Sulu\Bundle\RouteBundle\Model\RouteInterface;
use Sulu\Component\Content\Exception\ResourceLocatorAlreadyExistsException;
use Sulu\Component\Content\Metadata\Factory\StructureMetadataFactoryInterface;
use Sulu\Component\DocumentManager\Behavior\Mapping\ChildrenBehavior;
use Sulu\Component\DocumentManager\Behavior\Mapping\ParentBehavior;
use Sulu\Component\DocumentManager\DocumentManagerInterface;
use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
use Sulu\Component\DocumentManager\Event\CopyEvent;
use Sulu\Component\DocumentManager\Event\PublishEvent;
use Sulu\Component\DocumentManager\Event\RemoveEvent;
use Sulu\Component\DocumentManager\Event\ReorderEvent;
use Sulu\Component\DocumentManager\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Handles document-manager events to create/update/remove routes.
 */
class RoutableSubscriber implements EventSubscriberInterface
{
    const ROUTE_FIELD = 'routePath';

    const ROUTES_PROPERTY = 'suluRoutes';

    const TAG_NAME = 'sulu_article.article_route';

    /**
     * @var ChainRouteGeneratorInterface
     */
    private $chainRouteGenerator;

    /**
     * @var RouteManagerInterface
     */
    private $routeManager;

    /**
     * @var RouteRepositoryInterface
     */
    private $routeRepository;

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @var DocumentManagerInterface
     */
    private $documentManager;

    /**
     * @var DocumentInspector
     */
    private $documentInspector;

    /**
     * @var PropertyEncoder
     */
    private $propertyEncoder;

    /**
     * @var StructureMetadataFactoryInterface
     */
    private $metadataFactory;

    /**
     * @var ConflictResolverInterface
     */
    private $conflictResolver;

    public function __construct(
        ChainRouteGeneratorInterface $chainRouteGenerator,
        RouteManagerInterface $routeManager,
        RouteRepositoryInterface $routeRepository,
        EntityManagerInterface $entityManager,
        DocumentManagerInterface $documentManager,
        DocumentInspector $documentInspector,
        PropertyEncoder $propertyEncoder,
        StructureMetadataFactoryInterface $metadataFactory,
        ConflictResolverInterface $conflictResolver
    ) {
        $this->chainRouteGenerator = $chainRouteGenerator;
        $this->routeManager = $routeManager;
        $this->routeRepository = $routeRepository;
        $this->entityManager = $entityManager;
        $this->documentManager = $documentManager;
        $this->documentInspector = $documentInspector;
        $this->propertyEncoder = $propertyEncoder;
        $this->metadataFactory = $metadataFactory;
        $this->conflictResolver = $conflictResolver;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents(): array
    {
        return [
            Events::PERSIST => [
                // low priority because all other subscriber should be finished
                ['handlePersist', -2048],
            ],
            Events::REMOVE => [
                // high priority to ensure nodes are not deleted until we iterate over children
                ['handleRemove', 2048],
            ],
            Events::PUBLISH => ['handlePublish', -2048],
            Events::REORDER => ['handleReorder', -1024],
            Events::COPY => ['handleCopy', -2048],
        ];
    }

    /**
     * Generate route and save route-path.
     */
    public function handlePersist(AbstractMappingEvent $event): void
    {
        $document = $event->getDocument();

        if (!$document instanceof RoutablePageBehavior || !$document instanceof ChildrenBehavior) {
            return;
        }

        $this->updateChildRoutes($document);
    }

    /**
     * Regenerate routes for siblings on reorder.
     */
    public function handleReorder(ReorderEvent $event): void
    {
        $document = $event->getDocument();
        if (!$document instanceof RoutablePageBehavior || !$document instanceof ParentBehavior) {
            return;
        }

        $parentDocument = $document->getParent();
        if (!$parentDocument instanceof ChildrenBehavior) {
            return;
        }

        $this->updateChildRoutes($parentDocument);
    }

    /**
     * Handle publish event and generate route and the child-routes.
     *
     * @throws ResourceLocatorAlreadyExistsException
     */
    public function handlePublish(PublishEvent $event): void
    {
        $document = $event->getDocument();
        if (!$document instanceof RoutableBehavior) {
            return;
        }

        $propertyName = $this->getPropertyName($event->getLocale(), self::ROUTES_PROPERTY);

        // check if nodes previous generated routes exists and remove them if not
        $oldRoutes = $event->getNode()->getPropertyValueWithDefault($propertyName, []);
        $this->removeOldChildRoutes($event->getNode()->getSession(), $oldRoutes, $event->getLocale());

        $routes = [];
        if ($document instanceof ChildrenBehavior) {
            // generate new routes of children
            $routes = $this->generateChildRoutes($document, $event->getLocale());
        }

        // save the newly generated routes of children
        $event->getNode()->setProperty($propertyName, $routes);
        $this->entityManager->flush();
    }

    /**
     * Removes route.
     */
    public function handleRemove(RemoveEvent $event): void
    {
        $document = $event->getDocument();
        if (!$document instanceof RoutableBehavior || !$document instanceof ChildrenBehavior) {
            return;
        }

        $locales = $this->documentInspector->getLocales($document);
        foreach ($locales as $locale) {
            $this->removeChildRoutes($document, $locale);
        }

        $this->entityManager->flush();
    }

    /**
     * Update routes for copied article.
     */
    public function handleCopy(CopyEvent $event): void
    {
        $document = $event->getDocument();
        if (!$document instanceof RoutableBehavior) {
            return;
        }

        $locales = $this->documentInspector->getLocales($document);
        foreach ($locales as $locale) {
            $localizedDocument = $this->documentManager->find($event->getCopiedPath(), $locale);

            if ($localizedDocument instanceof ChildrenBehavior) {
                $this->generateChildRoutes($localizedDocument, $locale);
            }
        }
    }

    /**
     * Create or update for given document.
     */
    private function createOrUpdatePageRoute(RoutablePageBehavior $document, string $locale): RouteInterface
    {
        $route = $this->reallocateExistingRoute($document, $locale);
        if ($route) {
            return $route;
        }

        $route = $document->getRoute();
        if (!$route) {
            $route = $this->routeRepository->findByEntity($document->getClass(), $document->getUuid(), $locale);
        }

        if ($route && $route->getEntityId() !== $document->getId()) {
            // Mismatch of entity-id's happens because doctrine don't check entities which has been changed in the
            // current session.

            $document->removeRoute();
            $route = null;
        }

        if ($route) {
            $document->setRoute($route);

            return $this->routeManager->update($document, null, false);
        }

        return $this->routeManager->create($document);
    }

    /**
     * Reallocates existing route to given document.
     */
    private function reallocateExistingRoute(RoutablePageBehavior $document, string $locale): ?RouteInterface
    {
        $newRoute = $this->routeRepository->findByPath($document->getRoutePath(), $locale);
        if (!$newRoute) {
            return null;
        }

        $oldRoute = $this->routeRepository->findByEntity(get_class($document), $document->getUuid(), $locale);
        $history = $this->routeRepository->findHistoryByEntity(get_class($document), $document->getUuid(), $locale);

        /** @var RouteInterface $historyRoute */
        foreach (array_filter(array_merge($history, [$oldRoute])) as $historyRoute) {
            if ($historyRoute->getId() === $newRoute->getId() || $document->getId() !== $historyRoute->getEntityId()) {
                // Mismatch of entity-id's happens because doctrine don't check entities which has been changed in the
                // current session. If the old-route was already reused by a page before it will be returned in the
                // query of line 329.

                continue;
            }

            $historyRoute->setTarget($newRoute);
            $historyRoute->setHistory(true);
            $newRoute->addHistory($historyRoute);
        }

        $newRoute->setEntityClass(get_class($document));
        $newRoute->setEntityId($document->getId());
        $newRoute->setTarget(null);
        $newRoute->setHistory(false);

        return $newRoute;
    }

    private function updateRoute(RoutablePageBehavior $document): void
    {
        $locale = $this->documentInspector->getLocale($document);
        $propertyName = $this->getRoutePathPropertyName($document->getStructureType(), $locale);

        $route = $this->chainRouteGenerator->generate($document);
        $document->setRoutePath($route->getPath());

        $node = $this->documentInspector->getNode($document);
        $node->setProperty($propertyName, $route->getPath());
    }

    private function updateChildRoutes(ChildrenBehavior $document): void
    {
        foreach ($document->getChildren() as $childDocument) {
            if (!$childDocument instanceof RoutablePageBehavior) {
                continue;
            }

            $this->updateRoute($childDocument);
        }
    }

    /**
     * Generates child routes.
     *
     * @return string[]
     */
    private function generateChildRoutes(ChildrenBehavior $document, string $locale): array
    {
        $routes = [];
        foreach ($document->getChildren() as $child) {
            if (!$child instanceof RoutablePageBehavior) {
                continue;
            }

            $childRoute = $this->createOrUpdatePageRoute($child, $locale);
            $this->entityManager->persist($childRoute);

            $child->setRoutePath($childRoute->getPath());
            $childNode = $this->documentInspector->getNode($child);

            $propertyName = $this->getRoutePathPropertyName($child->getStructureType(), $locale);
            $childNode->setProperty($propertyName, $childRoute->getPath());

            $routes[] = $childRoute->getPath();
        }

        return $routes;
    }

    /**
     * Removes old-routes where the node does not exists anymore.
     */
    private function removeOldChildRoutes(SessionInterface $session, array $oldRoutes, string $locale): void
    {
        foreach ($oldRoutes as $oldRoute) {
            $oldRouteEntity = $this->routeRepository->findByPath($oldRoute, $locale);
            if ($oldRouteEntity && !$this->nodeExists($session, $oldRouteEntity->getEntityId())) {
                $this->entityManager->remove($oldRouteEntity);
            }
        }

        $this->entityManager->flush();
    }

    /**
     * Iterate over children and remove routes.
     */
    private function removeChildRoutes(ChildrenBehavior $document, string $locale): void
    {
        foreach ($document->getChildren() as $child) {
            if ($child instanceof RoutablePageBehavior) {
                $this->removeChildRoute($child, $locale);
            }

            if ($child instanceof ChildrenBehavior) {
                $this->removeChildRoutes($child, $locale);
            }
        }
    }

    /**
     * Removes route if exists.
     */
    private function removeChildRoute(RoutablePageBehavior $document, string $locale): void
    {
        $route = $this->routeRepository->findByPath($document->getRoutePath(), $locale);
        if ($route) {
            $this->entityManager->remove($route);
        }
    }

    /**
     * Returns encoded "routePath" property-name.
     */
    private function getRoutePathPropertyName(string $structureType, string $locale): string
    {
        $metadata = $this->metadataFactory->getStructureMetadata('article', $structureType);

        if ($metadata->hasTag(self::TAG_NAME)) {
            return $this->getPropertyName($locale, $metadata->getPropertyByTagName(self::TAG_NAME)->getName());
        }

        return $this->getPropertyName($locale, self::ROUTE_FIELD);
    }

    /**
     * Returns encoded property-name.
     */
    private function getPropertyName(string $locale, string $field): string
    {
        return $this->propertyEncoder->localizedSystemName($field, $locale);
    }

    /**
     * Returns true if given uuid exists.
     */
    private function nodeExists(SessionInterface $session, string $uuid): bool
    {
        try {
            $session->getNodeByIdentifier($uuid);

            return true;
        } catch (ItemNotFoundException $exception) {
            return false;
        }
    }
}


text:
<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Base application theme. -->
    <style name="AppTheme" parent="BaseTheme">
        <!-- Customize your theme here. -->
    </style>

    <style name="BaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:textColorPrimary">@color/white</item>
        <item name="colorPrimary">@color/primary</item>

        <item name="colorPrimaryDark">@color/primary_dark</item>

        <item name="colorAccent">@color/accent</item>
        <item name="colorControlActivated">@color/primary_dark</item>
        <item name="colorControlHighlight">@color/primary_dark</item>

        <item name="selectableItemBackground">@drawable/selectable_item_background</item>
        <item name="android:selectableItemBackground">@drawable/selectable_item_background</item>

        <item name="android:windowNoTitle">true</item>
        <item name="windowActionBar">false</item>
    </style>

    <style name="AppTheme.ActionBar.WithDrawer" parent="AppTheme">
        <item name="android:windowDrawsSystemBarBackgrounds" tools:targetApi="21">false</item>
        <item name="android:windowTranslucentStatus" tools:targetApi="21">true</item>
    </style>

    <style name="AppTheme.ActionBar" parent="AppTheme">
        <item name="android:windowDrawsSystemBarBackgrounds" tools:targetApi="21">true</item>
        <item name="android:windowTranslucentStatus" tools:targetApi="21">true</item>
    </style>

    <!--
    <color name="primary">#00BCD4</color>
    <color name="primary_dark">#0097A7</color>
    <color name="accent">#FFEB3B</color>
    <color name="accent_translucent">#80FFEB3B</color>
    <color name="accent_bright">#FFF493</color>
    -->
    <color name="accent_translucent">#80FFEB3B</color>

    <style name="nutrient_square">
        <item name="android:layout_width">12dp</item>
        <item name="android:layout_height">12dp</item>
        <item name="android:layout_marginStart">24dp</item>
        <item name="android:layout_marginLeft">24dp</item>
    </style>

    <style name="nutrient_text">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_weight">1</item>
        <item name="android:layout_marginStart">16dp</item>
        <item name="android:layout_marginLeft">16dp</item>
        <item name="android:gravity">left</item>
        <item name="android:textSize">20sp</item>
    </style>

    <style name="nutrient_value">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_weight">1</item>
        <item name="android:gravity">center</item>
    </style>

    <style name="nutrient_separator">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">1dp</item>
        <item name="android:layout_marginTop">4dp</item>
        <item name="android:layout_marginBottom">4dp</item>
        <item name="android:layout_marginStart">16dp</item>
        <item name="android:layout_marginLeft">16dp</item>
        <item name="android:layout_marginEnd">16dp</item>
        <item name="android:layout_marginRight">16dp</item>
        <item name="android:background">@color/material_grey_300</item>
    </style>

    <!-- -->

</resources>


text:
class AddNameToDopaCorePlayer < ActiveRecord::Migration
  def change
    add_column :dopa_core_players, :name, :string
  end
end


text:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>DamageIndicator</groupId>
    <artifactId>DamageIndicator</artifactId>
    <version>1.1.3</version>
    <build>
        <finalName>${project.name}-${project.version}</finalName>
        <directory>bin</directory>
        <sourceDirectory>src</sourceDirectory>
        <resources>
            <resource>
                <targetPath>.</targetPath>
                <filtering>true</filtering>
                <directory>src/resources/</directory>
                <includes>
                    <include>*.yml</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spigot-repo</id>
            <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.spigotmc</groupId>
            <artifactId>spigot-api</artifactId>
            <version>1.11.2-R0.1-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>