text:
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript" src="../app/model.js"></script>
    <script type="text/javascript" src="../app/view.js"></script>
    <script type="text/javascript" src="../app/controller.js"></script>
    <script type="text/javascript" src="utilities.js"></script>
    <script type="text/javascript" src="tests.js"></script>
  </head>
  <body>
  </body>
</html>


text:
class Player;
class Random;
class MobFactory;
class SoundPlayer;
class ChangeDimensionRequest;
class BlockSource;
class FullBlock;
class Difficulty;
class EntityDamageSource;
class LightLayer;
class LevelChunk;
class Vec3;
class AABB;
class Material;
typedef int EntityUniqueID;
class LevelListener;
class LevelEvent;
class LevelSettings;
typedef int StorageVersion;
// Size : 2996
class Level : public BlockSourceListener, public AppPlatformListener
{
public:
	//void **vtable;						// 4
	char filler1[4];						// 8
	bool isRemote;							// 12
	//std::unordered_set<Player *> players;	// 60
	char filler2[80];						// 16
	//Random random;						// 96 + (SIZE??)
	char filler3[2664];						// 96
	LevelData worldInfo;					// 2760
	char filler4[32];						// 2868
	MobFactory *mobFactory;					// 2900
	char filler5[4];						// 2904
	BlockSource *tileSource;				// 2908
	char filler6[84];						// 2912
public:
	Level(SoundPlayer &, std::unique_ptr<LevelStorage>, std::string const &, LevelSettings const &, bool);
	virtual ~Level();
	virtual void onSourceCreated(BlockSource &);
	virtual void onSourceDestroyed(BlockSource &);
	virtual void onBlockChanged(BlockSource &, BlockPos const &, FullBlock, FullBlock, int);
	virtual void addEntity(std::unique_ptr<Entity>&);
	virtual void addPlayer(std::unique_ptr<Player>);
	virtual void addGlobalEntity(std::unique_ptr<Entity>);
	virtual void onPlayerDeath(Player &, EntityDamageSource const &);
	virtual void tick();
	virtual void directTickEntities(BlockSource &);
	virtual void updateSleepingPlayerList();
	virtual void setDifficulty(Difficulty);
	virtual void runLightUpdates(BlockSource &, LightLayer const &, BlockPos const &, BlockPos const &);
	virtual void onNewChunkFor(Player &, LevelChunk &);
	virtual void onChunkLoaded(LevelChunk &);
	virtual void removeEntity(std::unique_ptr<Entity> &, bool);
	virtual void removeEntity(Entity &, bool);
	virtual void onAppSuspended();
	void _backgroundTickSeasons();
	void _chunkDirty(BlockSource *, LevelChunk *);
	void _cleanupDisconnectedPlayers();
	void _destroyEffect(BlockPos const &, FullBlock, Vec3 const &, float);
	void _handleChangeDimensionRequests();
	void _playerChangeDimension(Player *, ChangeDimensionRequest &);
	void _removeAllPlayers();
	void _saveSomeDirtyChunks();
	void _syncTime(int);
	void _tickTemporaryPointers();
	void addListener(LevelListener &);
	void addParticle(ParticleType, Vec3 const &, Vec3 const &, int);
	void animateTick(Entity &);
	void broadcastDimensionEvent(BlockSource &, LevelEvent, Vec3 const &, int, Player *);
	void broadcastEntityEvent(Entity *, EntityEvent);
	void broadcastLevelEvent(LevelEvent, Vec3 const &, int, Player *);
	void createDimension(DimensionId);
	void destroyBlock(BlockSource &, BlockPos const &, bool);
	void destroyEffect(BlockSource &, int, int, int, Vec3 const &, float);
	void entityChangeDimension(Entity &, DimensionId);
	void explode(BlockSource &, Entity *, Vec3 const&, float, bool);
	void extinguishFire(BlockSource &, int, int, int, signed char);
	void findPath(Entity &, Entity &, float, bool, bool, bool, bool);
	void findPath(Entity &, int, int, int, float, bool, bool, bool, bool);
	void forceRemoveEntity(Entity &);
	void getAdventureSettings();
	void getCurrentTick();
	void getDefaultSpawn();
	int getDifficulty()const;
	void getDimension(DimensionId);
	//void getEntities(DimensionId, EntityType, AABB const &, std::vector<Entity *> &);
	void getEntity(EntityUniqueID, bool);
	void getGlobalEntities();
	void getHitResult();
	LevelData* getLevelData();
	void getLightsToUpdate();
	LocalPlayer *getLocalPlayer() const ;
	void getMob(EntityUniqueID);
	void getMobSpawner();
	void getNearestAttackablePlayer(Entity &, float);
	void getNearestAttackablePlayer(float, float, float, float);
	void getNearestPlayer(Entity &, float);
	void getNearestPlayer(float, float, float, float);
	void getNetEventCallback();
	void getNewUniqueID();
	void getNextPlayer(EntityUniqueID const &, bool);
	void getNumRemotePlayers();
	void getPacketSender();
	void getPlayer(EntityUniqueID);
	//void getPlayer(mce::UUID const &);
	void getPlayer(std::string const &);
	void getPlayerList();
	void getPlayerNames();
	void getPlayers();
	void getPortalForcer();
	void getPrevPlayer(EntityUniqueID const &, bool);
	void getRandom();
	void getRandomPlayer();
	void getSeaLevel();
	long getSeed();
	void getSharedSpawnPos();
	void getSpecialMultiplier(DimensionId);
	void getTearingDown();
	int getTime() const;
	void setTime(int);
	void getVillages();
	void handleLevelEvent(LevelEvent, Vec3 const &, int);
	void hasLevelStorage();
	void isClientSide();
	void isDayCycleActive();
	bool isNightMode()const;
	void isUpdatingLights();
	void loadPlayer(std::unique_ptr<Player>);
	void mayInteract(Player &, int, int, int);
	void onChunkDiscarded(LevelChunk &);
	void playSound(Entity *, std::string const &, float, float);
	void playSound(Vec3 const &, std::string const &, float, float);
	void potionSplash(Vec3 const &, int, bool);
	//void registerTemporaryPointer(_TickPtr &);
	void removeListener(LevelListener &);
	//void requestPlayerChangeDimension(Player &, std::unique_ptr<ChangeDimensionRequest>);
	void saveBiomeData();
	void saveDirtyChunks();
	void saveGameData();
	void saveLevelData();
	void savePlayers();
	void setDayCycleActive(bool);
	void setDefaultSpawn(BlockPos const &);
	void setIsClientSide(bool);
	//void setNetEventCallback(NetEventCallback *);
	void setNightMode(bool);
	//void setPacketSender(PacketSender *);
	void setSpawnSettings(bool, bool);
	void setStopTime(int);
	void tickEntities();
	//void unregisterTemporaryPointer(_TickPtr &);
	void updateLights();
	LevelStorage*getLevelStorage();
	void upgradeStorageVersion(StorageVersion);
};

redpajama_set_name: RedPajamaGithub


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Soldier.Extension.AutoLogin.View
{
    /// <summary>
    /// Interaction logic for SystemLogin.xaml
    /// </summary>
    public partial class SystemLogin : UserControl
    {
        public delegate void ClickHandler();
        public event ClickHandler Click;

        public SystemLogin() : this("")
        {

        }

        public SystemLogin(string Title)
        {
            this.Title = Title;
            InitializeComponent();
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            Click?.Invoke();
            e.Handled = false;
        }

        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(SystemLogin));
    }
}


text:
package org.apache.flink.table.planner.plan.stream.sql.join

import org.apache.flink.table.api._
import org.apache.flink.table.planner.utils.{StreamTableTestUtil, TableTestBase}

import org.junit.Test

/**
 * Tests for window join.
 */
class WindowJoinTest extends TableTestBase {

  private val util: StreamTableTestUtil = streamTestUtil()
  util.tableEnv.executeSql(
    s"""
       |CREATE TABLE MyTable (
       |  a INT,
       |  b STRING NOT NULL,
       |  c BIGINT,
       |  rowtime TIMESTAMP(3),
       |  proctime as PROCTIME(),
       |  WATERMARK FOR rowtime AS rowtime - INTERVAL '1' SECOND
       |) with (
       |  'connector' = 'values'
       |)
       |""".stripMargin)

  util.tableEnv.executeSql(
    s"""
       |CREATE TABLE MyTable2 (
       |  a INT,
       |  b STRING NOT NULL,
       |  c BIGINT,
       |  rowtime TIMESTAMP(3),
       |  proctime as PROCTIME(),
       |  WATERMARK FOR rowtime AS rowtime - INTERVAL '1' SECOND
       |) with (
       |  'connector' = 'values'
       |)
       |""".stripMargin)

  // ----------------------------------------------------------------------------------------
  // Tests for queries Join on window TVF
  // Current does not support merge Window TVF into WindowJoin.
  // ----------------------------------------------------------------------------------------

  @Test
  def testCantMergeWindowTVF_Tumble(): Unit = {
    val sql =
      """
        |SELECT L.a, L.b, L.c, R.a, R.b, R.c
        |FROM (
        |  SELECT *
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testCantMergeWindowTVF_TumbleOnProctime(): Unit = {
    val sql =
      """
        |SELECT L.a, L.b, L.c, R.a, R.b, R.c
        |FROM (
        |  SELECT *
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '15' MINUTE))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '15' MINUTE))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testCantMergeWindowTVF_Hop(): Unit = {
    val sql =
      """
        |SELECT L.a, L.b, L.c, R.a, R.b, R.c
        |FROM (
        |  SELECT *
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(
        |  HOP(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testCantMergeWindowTVF_HopOnProctime(): Unit = {
    val sql =
      """
        |SELECT L.a, L.b, L.c, R.a, R.b, R.c
        |FROM (
        |  SELECT *
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(
        |  HOP(TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testCantMergeWindowTVF_Cumulate(): Unit = {
    val sql =
      """
        |SELECT L.a, L.b, L.c, R.a, R.b, R.c
        |FROM (
        |  SELECT *
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testCantMergeWindowTVF_CumulateOnProctime(): Unit = {
    val sql =
      """
        |SELECT L.a, L.b, L.c, R.a, R.b, R.c
        |FROM (
        |  SELECT *
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  // ----------------------------------------------------------------------------------------
  // Tests for invalid queries Join on window Aggregate
  // because left window strategy is not equals to right window strategy.
  // ----------------------------------------------------------------------------------------

  /** Window type in left and right child should be same **/
  @Test
  def testNotSameWindowType(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join doesn't support different window table function of left and " +
        "right inputs.\n" +
        "The left window table function is HOP(size=[10 min], slide=[5 min]).\n" +
        "The right window table function is CUMULATE(max_size=[1 h], step=[10 min]).")
    util.verifyRelPlan(sql)
  }

  /** Window spec in left and right child should be same **/
  @Test
  def testNotSameWindowSpec(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '2' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  CUMULATE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join doesn't support different window table function of left and " +
        "right inputs.\n" +
        "The left window table function is CUMULATE(max_size=[2 h], step=[10 min]).\n" +
        "The right window table function is CUMULATE(max_size=[1 h], step=[10 min]).")
    util.verifyRelPlan(sql)
  }

  /** Window spec in left and right child should be same **/
  @Test
  def testNotSameTimeAttributeType(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join doesn't support different time attribute type of left and " +
        "right inputs.\n" +
        "The left time attribute type is TIMESTAMP_LTZ(3) NOT NULL *PROCTIME*.\n" +
        "The right time attribute type is TIMESTAMP(3) *ROWTIME*.")
    util.verifyRelPlan(sql)
  }

  // ----------------------------------------------------------------------------------------
  // Window starts equality and window ends equality are both required for window join.
  // TODO: In the future, we could support join clause which only includes window starts
  //  equality or window ends equality for TUMBLE or HOP window.
  // ----------------------------------------------------------------------------------------

  @Test
  def testMissWindowEndInConditionForTumbleWindow(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join requires JOIN ON condition must contain both window starts " +
        "equality of input tables and window ends equality of input tables.\n" +
        "But the current JOIN ON condition is ((window_start = window_start) AND (a = a)).")
    util.verifyRelPlan(sql)
  }

  @Test
  def testMissWindowStartInConditionForTumbleWindow(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_end = R.window_end AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join requires JOIN ON condition must contain both window starts " +
        "equality of input tables and window ends equality of input tables.\n" +
        "But the current JOIN ON condition is ((window_end = window_end) AND (a = a)).")
    util.verifyRelPlan(sql)
  }

  @Test
  def testMissWindowEndInConditionForHopWindow(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join requires JOIN ON condition must contain both window starts " +
        "equality of input tables and window ends equality of input tables.\n" +
        "But the current JOIN ON condition is ((window_start = window_start) AND (a = a)).")
    util.verifyRelPlan(sql)
  }

  @Test
  def testMissWindowStartInConditionForHopWindow(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_end = R.window_end AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join requires JOIN ON condition must contain both window starts " +
        "equality of input tables and window ends equality of input tables.\n" +
        "But the current JOIN ON condition is ((window_end = window_end) AND (a = a)).")
    util.verifyRelPlan(sql)
  }

  @Test
  def testMissWindowEndInConditionForCumulateWindow(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join requires JOIN ON condition must contain both window starts " +
        "equality of input tables and window ends equality of input tables.\n" +
        "But the current JOIN ON condition is ((window_start = window_start) AND (a = a)).")
    util.verifyRelPlan(sql)
  }

  @Test
  def testMissWindowStartInConditionForCumulateWindow(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_end = R.window_end AND L.a = R.a
      """.stripMargin

    thrown.expect(classOf[TableException])
    thrown.expectMessage(
      "Currently, window join requires JOIN ON condition must contain both window starts " +
        "equality of input tables and window ends equality of input tables.\n" +
        "But the current JOIN ON condition is ((window_end = window_end) AND (a = a)).")
    util.verifyRelPlan(sql)
  }

  // ----------------------------------------------------------------------------------------
  // Tests for valid queries Window Join on window Aggregate.
  // ----------------------------------------------------------------------------------------

  @Test
  def testOnTumbleWindowAggregate(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testOnTumbleWindowAggregateOnProctime(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testOnHopWindowAggregate(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testOnHopWindowAggregateOnProctime(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |  HOP(TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '5' MINUTE, INTERVAL '10' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testOnCumulateWindowAggregate(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testOnCumulateWindowAggregateOnProctime(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testWindowJoinWithNonEqui(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(proctime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a AND
        | CAST(L.window_start AS BIGINT) > R.uv
      """.stripMargin
    util.verifyRelPlan(sql)
  }
  // ----------------------------------------------------------------------------------------
  // Window Join could propagate time attribute
  // ----------------------------------------------------------------------------------------

  @Test
  def testTimeAttributePropagateForWindowJoin(): Unit = {
    util.tableEnv.executeSql(
      s"""
         |CREATE TABLE MyTable3 (
         |  a INT,
         |  b STRING NOT NULL,
         |  c BIGINT,
         |  rowtime TIMESTAMP(3),
         |  proctime as PROCTIME(),
         |  WATERMARK FOR rowtime AS rowtime - INTERVAL '1' SECOND
         |) with (
         |  'connector' = 'values'
         |)
         |""".stripMargin)

    util.tableEnv.executeSql(
      """
        |CREATE VIEW tmp AS
        |SELECT
        |  L.window_time as rowtime,
        |  L.a as a,
        |  L.b as l_b,
        |  L.c as l_c,
        |  R.b as r_b,
        |  R.c as r_c
        |FROM (
        |  SELECT *
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |) L
        |JOIN (
        |  SELECT *
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin)

    val sql =
      """
        |SELECT tmp.*, MyTable3.* FROM tmp JOIN MyTable3 ON
        | tmp.a = MyTable3.a AND
        | tmp.rowtime BETWEEN
        |   MyTable3.rowtime - INTERVAL '10' SECOND AND
        |   MyTable3.rowtime + INTERVAL '1' HOUR
        |""".stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testTimeAttributePropagateForWindowJoin1(): Unit = {
    util.tableEnv.executeSql(
      s"""
         |CREATE TABLE MyTable4 (
         |  a INT,
         |  b STRING NOT NULL,
         |  c BIGINT,
         |  rowtime TIMESTAMP(3),
         |  proctime as PROCTIME(),
         |  WATERMARK FOR rowtime AS rowtime - INTERVAL '1' SECOND
         |) with (
         |  'connector' = 'values'
         |)
         |""".stripMargin)

    util.tableEnv.executeSql(
      """
        |CREATE VIEW tmp1 AS
        |SELECT
        |  L.window_time as rowtime,
        |  L.a,
        |  L.cnt as l_cnt,
        |  L.uv as l_uv,
        |  R.cnt as r_cnt,
        |  R.uv as r_uv
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin)

    val sql =
      """
        |SELECT tmp1.*, MyTable4.* FROM tmp1 JOIN MyTable4 ON
        | tmp1.a = MyTable4.a AND
        | tmp1.rowtime BETWEEN
        |   MyTable4.rowtime - INTERVAL '10' SECOND AND
        |   MyTable4.rowtime + INTERVAL '1' HOUR
        |""".stripMargin
    util.verifyRelPlan(sql)
  }

  // ----------------------------------------------------------------------------------------
  // Window Join could propagate window properties
  // ----------------------------------------------------------------------------------------

  @Test
  def testWindowPropertyPropagateForWindowJoin(): Unit = {
    util.tableEnv.executeSql(
      """
        |CREATE VIEW tmp2 AS
        |SELECT
        |  L.window_start as window_start,
        |  L.window_end as window_end,
        |  L.a,
        |  L.cnt as l_cnt,
        |  L.uv as l_uv,
        |  R.cnt as r_cnt,
        |  R.uv as r_uv
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(
        |    CUMULATE(
        |      TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '10' MINUTE, INTERVAL '1' HOUR))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a
      """.stripMargin)

    val sql =
      """
        |SELECT * FROM
        |(
        |  SELECT *,
        |    ROW_NUMBER() OVER(
        |      PARTITION BY window_start, window_end ORDER BY l_cnt DESC) as rownum
        |  FROM tmp2
        |)
        |WHERE rownum <= 3
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  // ----------------------------------------------------------------------------------------
  // Semi/AntiJoin
  // ----------------------------------------------------------------------------------------

  @Test
  def testSemiJoinIN(): Unit = {
    val sql =
      """
        |SELECT * FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L WHERE L.a IN (
        |SELECT a FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |WHERE L.window_start = R.window_start AND L.window_end = R.window_end)
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testSemiExist(): Unit = {
    val sql =
      """
        |SELECT * FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L WHERE EXISTS (
        |SELECT * FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |WHERE L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a)
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testAntiJoinNotIN(): Unit = {
    val sql =
      """
        |SELECT * FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L WHERE L.a NOT IN (
        |SELECT a FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |WHERE L.window_start = R.window_start AND L.window_end = R.window_end)
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  @Test
  def testAntiJoinNotExist(): Unit = {
    val sql =
      """
        |SELECT * FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L WHERE NOT EXISTS (
        |SELECT * FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |WHERE L.window_start = R.window_start AND L.window_end = R.window_end AND L.a = R.a)
      """.stripMargin
    util.verifyRelPlan(sql)
  }

  // ----------------------------------------------------------------------------------------
  // Test IS NOT DISTINCT FROM
  // ----------------------------------------------------------------------------------------

  @Test
  def testJoinWithIsNotDistinctFrom(): Unit = {
    val sql =
      """
        |SELECT L.*, R.*
        |FROM (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) L
        |JOIN (
        |  SELECT
        |    a,
        |    window_start,
        |    window_end,
        |    window_time,
        |    count(*) as cnt,
        |    count(distinct c) AS uv
        |  FROM TABLE(TUMBLE(TABLE MyTable2, DESCRIPTOR(rowtime), INTERVAL '15' MINUTE))
        |  GROUP BY a, window_start, window_end, window_time
        |) R
        |ON L.window_start = R.window_start AND L.window_end = R.window_end AND
        |L.a IS NOT DISTINCT FROM R.a
      """.stripMargin
    util.verifyRelPlan(sql)
  }
}


text:
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "webkit/media/webmediaplayer_impl.h"

#include <limits>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/message_loop_proxy.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "media/audio/null_audio_sink.h"
#include "media/base/filter_collection.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/pipeline.h"
#include "media/base/video_frame.h"
#include "media/filters/audio_renderer_impl.h"
#include "media/filters/video_renderer_base.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebRect.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURL.h"
#include "v8/include/v8.h"
#include "webkit/media/buffered_data_source.h"
#include "webkit/media/filter_helpers.h"
#include "webkit/media/webmediaplayer_delegate.h"
#include "webkit/media/webmediaplayer_proxy.h"
#include "webkit/media/webmediaplayer_util.h"
#include "webkit/media/webvideoframe_impl.h"

using WebKit::WebCanvas;
using WebKit::WebMediaPlayer;
using WebKit::WebRect;
using WebKit::WebSize;
using WebKit::WebString;
using media::PipelineStatus;

namespace {

// Amount of extra memory used by each player instance reported to V8.
// It is not exact number -- first, it differs on different platforms,
// and second, it is very hard to calculate. Instead, use some arbitrary
// value that will cause garbage collection from time to time. We don't want
// it to happen on every allocation, but don't want 5k players to sit in memory
// either. Looks that chosen constant achieves both goals, at least for audio
// objects. (Do not worry about video objects yet, JS programs do not create
// thousands of them...)
const int kPlayerExtraMemory = 1024 * 1024;

// Limits the range of playback rate.
//
// TODO(kylep): Revisit these.
//
// Vista has substantially lower performance than XP or Windows7.  If you speed
// up a video too much, it can't keep up, and rendering stops updating except on
// the time bar. For really high speeds, audio becomes a bottleneck and we just
// use up the data we have, which may not achieve the speed requested, but will
// not crash the tab.
//
// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems
// like a busy loop). It gets unresponsive, although its not completely dead.
//
// Also our timers are not very accurate (especially for ogg), which becomes
// evident at low speeds and on Vista. Since other speeds are risky and outside
// the norms, we think 1/16x to 16x is a safe and useful range for now.
const float kMinRate = 0.0625f;
const float kMaxRate = 16.0f;

}  // namespace

namespace webkit_media {

#define COMPILE_ASSERT_MATCHING_ENUM(name) \
  COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayer::CORSMode ## name) == \
                 static_cast<int>(BufferedResourceLoader::k ## name), \
                 mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(Unspecified);
COMPILE_ASSERT_MATCHING_ENUM(Anonymous);
COMPILE_ASSERT_MATCHING_ENUM(UseCredentials);
#undef COMPILE_ASSERT_MATCHING_ENUM

static WebKit::WebTimeRanges ConvertToWebTimeRanges(
    const media::Ranges<base::TimeDelta>& ranges) {
  WebKit::WebTimeRanges result(ranges.size());
  for (size_t i = 0; i < ranges.size(); i++) {
    result[i].start = ranges.start(i).InSecondsF();
    result[i].end = ranges.end(i).InSecondsF();
  }
  return result;
}

WebMediaPlayerImpl::WebMediaPlayerImpl(
    WebKit::WebFrame* frame,
    WebKit::WebMediaPlayerClient* client,
    base::WeakPtr<WebMediaPlayerDelegate> delegate,
    media::FilterCollection* collection,
    WebKit::WebAudioSourceProvider* audio_source_provider,
    media::AudioRendererSink* audio_renderer_sink,
    media::MessageLoopFactory* message_loop_factory,
    MediaStreamClient* media_stream_client,
    media::MediaLog* media_log)
    : frame_(frame),
      network_state_(WebMediaPlayer::NetworkStateEmpty),
      ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
      main_loop_(MessageLoop::current()),
      filter_collection_(collection),
      started_(false),
      message_loop_factory_(message_loop_factory),
      paused_(true),
      seeking_(false),
      playback_rate_(0.0f),
      pending_seek_(false),
      pending_seek_seconds_(0.0f),
      client_(client),
      proxy_(new WebMediaPlayerProxy(main_loop_->message_loop_proxy(), this)),
      delegate_(delegate),
      media_stream_client_(media_stream_client),
      media_log_(media_log),
      accelerated_compositing_reported_(false),
      incremented_externally_allocated_memory_(false),
      audio_source_provider_(audio_source_provider),
      audio_renderer_sink_(audio_renderer_sink),
      is_local_source_(false),
      decryptor_(proxy_.get()) {
  media_log_->AddEvent(
      media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED));

  MessageLoop* pipeline_message_loop =
      message_loop_factory_->GetMessageLoop("PipelineThread");
  pipeline_ = new media::Pipeline(pipeline_message_loop, media_log_);

  // Let V8 know we started new thread if we did not did it yet.
  // Made separate task to avoid deletion of player currently being created.
  // Also, delaying GC until after player starts gets rid of starting lag --
  // collection happens in parallel with playing.
  //
  // TODO(enal): remove when we get rid of per-audio-stream thread.
  MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&WebMediaPlayerImpl::IncrementExternallyAllocatedMemory,
                 AsWeakPtr()));

  // Also we want to be notified of |main_loop_| destruction.
  main_loop_->AddDestructionObserver(this);

  // Create default video renderer.
  scoped_refptr<media::VideoRendererBase> video_renderer =
      new media::VideoRendererBase(
          base::Bind(&WebMediaPlayerProxy::Repaint, proxy_),
          base::Bind(&WebMediaPlayerProxy::SetOpaque, proxy_.get()),
          true);
  filter_collection_->AddVideoRenderer(video_renderer);
  proxy_->set_frame_provider(video_renderer);

  // Create default audio renderer.
  filter_collection_->AddAudioRenderer(
      new media::AudioRendererImpl(new media::NullAudioSink()));
}

WebMediaPlayerImpl::~WebMediaPlayerImpl() {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  Destroy();
  media_log_->AddEvent(
      media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));

  if (delegate_)
    delegate_->PlayerGone(this);

  // Finally tell the |main_loop_| we don't want to be notified of destruction
  // event.
  if (main_loop_) {
    main_loop_->RemoveDestructionObserver(this);
  }
}

namespace {

// Helper enum for reporting scheme histograms.
enum URLSchemeForHistogram {
  kUnknownURLScheme,
  kMissingURLScheme,
  kHttpURLScheme,
  kHttpsURLScheme,
  kFtpURLScheme,
  kChromeExtensionURLScheme,
  kJavascriptURLScheme,
  kFileURLScheme,
  kBlobURLScheme,
  kDataURLScheme,
  kFileSystemScheme,
  kMaxURLScheme = kFileSystemScheme  // Must be equal to highest enum value.
};

URLSchemeForHistogram URLScheme(const GURL& url) {
  if (!url.has_scheme()) return kMissingURLScheme;
  if (url.SchemeIs("http")) return kHttpURLScheme;
  if (url.SchemeIs("https")) return kHttpsURLScheme;
  if (url.SchemeIs("ftp")) return kFtpURLScheme;
  if (url.SchemeIs("chrome-extension")) return kChromeExtensionURLScheme;
  if (url.SchemeIs("javascript")) return kJavascriptURLScheme;
  if (url.SchemeIs("file")) return kFileURLScheme;
  if (url.SchemeIs("blob")) return kBlobURLScheme;
  if (url.SchemeIs("data")) return kDataURLScheme;
  if (url.SchemeIs("filesystem")) return kFileSystemScheme;
  return kUnknownURLScheme;
}

}  // anonymous namespace

void WebMediaPlayerImpl::load(const WebKit::WebURL& url, CORSMode cors_mode) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GURL gurl(url);
  UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(gurl), kMaxURLScheme);

  // Handle any volume/preload changes that occured before load().
  setVolume(GetClient()->volume());
  setPreload(GetClient()->preload());

  SetNetworkState(WebMediaPlayer::NetworkStateLoading);
  SetReadyState(WebMediaPlayer::ReadyStateHaveNothing);
  media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));

  // Media streams pipelines can start immediately.
  if (BuildMediaStreamCollection(url, media_stream_client_,
                                 message_loop_factory_.get(),
                                 filter_collection_.get())) {
    StartPipeline();
    return;
  }

  // Media source pipelines can start immediately.
  scoped_refptr<media::FFmpegVideoDecoder> video_decoder;
  if (BuildMediaSourceCollection(url, GetClient()->sourceURL(), proxy_,
                                 message_loop_factory_.get(),
                                 filter_collection_.get(),
                                 &decryptor_,
                                 &video_decoder)) {
    proxy_->set_video_decoder(video_decoder);
    StartPipeline();
    return;
  }

  // Otherwise it's a regular request which requires resolving the URL first.
  proxy_->set_data_source(
      new BufferedDataSource(main_loop_, frame_, media_log_,
                             base::Bind(&WebMediaPlayerImpl::NotifyDownloading,
                                        base::Unretained(this))));
  proxy_->data_source()->Initialize(
      url, static_cast<BufferedResourceLoader::CORSMode>(cors_mode),
      base::Bind(
          &WebMediaPlayerImpl::DataSourceInitialized,
          base::Unretained(this), gurl));

  is_local_source_ = !gurl.SchemeIs("http") && !gurl.SchemeIs("https");

  BuildDefaultCollection(proxy_->data_source(),
                         message_loop_factory_.get(),
                         filter_collection_.get(),
                         &decryptor_,
                         &video_decoder);
  proxy_->set_video_decoder(video_decoder);
}

void WebMediaPlayerImpl::cancelLoad() {
  DCHECK_EQ(main_loop_, MessageLoop::current());
}

void WebMediaPlayerImpl::play() {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  paused_ = false;
  pipeline_->SetPlaybackRate(playback_rate_);

  media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY));

  if (delegate_)
    delegate_->DidPlay(this);
}

void WebMediaPlayerImpl::pause() {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  paused_ = true;
  pipeline_->SetPlaybackRate(0.0f);
  paused_time_ = pipeline_->GetCurrentTime();

  media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE));

  if (delegate_)
    delegate_->DidPause(this);
}

bool WebMediaPlayerImpl::supportsFullscreen() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  return true;
}

bool WebMediaPlayerImpl::supportsSave() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  return true;
}

void WebMediaPlayerImpl::seek(float seconds) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  if (seeking_) {
    pending_seek_ = true;
    pending_seek_seconds_ = seconds;
    return;
  }

  media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));

  base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);

  // Update our paused time.
  if (paused_)
    paused_time_ = seek_time;

  seeking_ = true;

  proxy_->DemuxerStartWaitingForSeek();

  // Kick off the asynchronous seek!
  pipeline_->Seek(
      seek_time,
      base::Bind(&WebMediaPlayerProxy::PipelineSeekCallback,
                 proxy_.get()));
}

void WebMediaPlayerImpl::setEndTime(float seconds) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // TODO(hclam): add method call when it has been implemented.
  return;
}

void WebMediaPlayerImpl::setRate(float rate) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // TODO(kylep): Remove when support for negatives is added. Also, modify the
  // following checks so rewind uses reasonable values also.
  if (rate < 0.0f)
    return;

  // Limit rates to reasonable values by clamping.
  if (rate != 0.0f) {
    if (rate < kMinRate)
      rate = kMinRate;
    else if (rate > kMaxRate)
      rate = kMaxRate;
  }

  playback_rate_ = rate;
  if (!paused_) {
    pipeline_->SetPlaybackRate(rate);
  }
}

void WebMediaPlayerImpl::setVolume(float volume) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  pipeline_->SetVolume(volume);
}

void WebMediaPlayerImpl::setVisible(bool visible) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // TODO(hclam): add appropriate method call when pipeline has it implemented.
  return;
}

#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \
    COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \
                   static_cast<int>(webkit_media::chromium_name), \
                   mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE);
COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA);
COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO);
#undef COMPILE_ASSERT_MATCHING_ENUM

void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  if (proxy_ && proxy_->data_source()) {
    // XXX: Why do I need to use webkit_media:: prefix? clang complains!
    proxy_->data_source()->SetPreload(
        static_cast<webkit_media::Preload>(preload));
  }
}

bool WebMediaPlayerImpl::totalBytesKnown() {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  return pipeline_->GetTotalBytes() != 0;
}

bool WebMediaPlayerImpl::hasVideo() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  return pipeline_->HasVideo();
}

bool WebMediaPlayerImpl::hasAudio() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  return pipeline_->HasAudio();
}

WebKit::WebSize WebMediaPlayerImpl::naturalSize() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  gfx::Size size;
  pipeline_->GetNaturalVideoSize(&size);
  return WebKit::WebSize(size);
}

bool WebMediaPlayerImpl::paused() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  return pipeline_->GetPlaybackRate() == 0.0f;
}

bool WebMediaPlayerImpl::seeking() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
    return false;

  return seeking_;
}

float WebMediaPlayerImpl::duration() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  base::TimeDelta duration = pipeline_->GetMediaDuration();

  // Return positive infinity if the resource is unbounded.
  // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
  if (duration == media::kInfiniteDuration())
    return std::numeric_limits<float>::infinity();

  return static_cast<float>(duration.InSecondsF());
}

float WebMediaPlayerImpl::currentTime() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  if (paused_)
    return static_cast<float>(paused_time_.InSecondsF());
  return static_cast<float>(pipeline_->GetCurrentTime().InSecondsF());
}

int WebMediaPlayerImpl::dataRate() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // TODO(hclam): Add this method call if pipeline has it in the interface.
  return 0;
}

WebMediaPlayer::NetworkState WebMediaPlayerImpl::networkState() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  return network_state_;
}

WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  return ready_state_;
}

const WebKit::WebTimeRanges& WebMediaPlayerImpl::buffered() {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  WebKit::WebTimeRanges web_ranges(
      ConvertToWebTimeRanges(pipeline_->GetBufferedTimeRanges()));
  buffered_.swap(web_ranges);
  return buffered_;
}

float WebMediaPlayerImpl::maxTimeSeekable() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // We don't support seeking in streaming media.
  if (proxy_ && proxy_->data_source() && proxy_->data_source()->IsStreaming())
    return 0.0f;
  return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF());
}

bool WebMediaPlayerImpl::didLoadingProgress() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  return pipeline_->DidLoadingProgress();
}

unsigned long long WebMediaPlayerImpl::totalBytes() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  return pipeline_->GetTotalBytes();
}

void WebMediaPlayerImpl::setSize(const WebSize& size) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // Don't need to do anything as we use the dimensions passed in via paint().
}

// This variant (without alpha) is just present during staging of this API
// change. Later we will again only have one virtual paint().
void WebMediaPlayerImpl::paint(WebKit::WebCanvas* canvas,
                               const WebKit::WebRect& rect) {
  paint(canvas, rect, 0xFF);
}

void WebMediaPlayerImpl::paint(WebCanvas* canvas,
                               const WebRect& rect,
                               uint8_t alpha) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  DCHECK(proxy_);

  if (!accelerated_compositing_reported_) {
    accelerated_compositing_reported_ = true;
    // Normally paint() is only called in non-accelerated rendering, but there
    // are exceptions such as webgl where compositing is used in the WebView but
    // video frames are still rendered to a canvas.
    UMA_HISTOGRAM_BOOLEAN(
        "Media.AcceleratedCompositingActive",
        frame_->view()->isAcceleratedCompositingActive());
  }

  proxy_->Paint(canvas, rect, alpha);
}

bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const {
  if (proxy_)
    return proxy_->HasSingleOrigin();
  return true;
}

bool WebMediaPlayerImpl::didPassCORSAccessCheck() const {
  return proxy_ && proxy_->DidPassCORSAccessCheck();
}

WebMediaPlayer::MovieLoadType WebMediaPlayerImpl::movieLoadType() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // Disable seeking while streaming.
  if (proxy_ && proxy_->data_source() && proxy_->data_source()->IsStreaming())
    return WebMediaPlayer::MovieLoadTypeLiveStream;
  return WebMediaPlayer::MovieLoadTypeUnknown;
}

float WebMediaPlayerImpl::mediaTimeForTimeValue(float timeValue) const {
  return ConvertSecondsToTimestamp(timeValue).InSecondsF();
}

unsigned WebMediaPlayerImpl::decodedFrameCount() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  media::PipelineStatistics stats = pipeline_->GetStatistics();
  return stats.video_frames_decoded;
}

unsigned WebMediaPlayerImpl::droppedFrameCount() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  media::PipelineStatistics stats = pipeline_->GetStatistics();
  return stats.video_frames_dropped;
}

unsigned WebMediaPlayerImpl::audioDecodedByteCount() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  media::PipelineStatistics stats = pipeline_->GetStatistics();
  return stats.audio_bytes_decoded;
}

unsigned WebMediaPlayerImpl::videoDecodedByteCount() const {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  media::PipelineStatistics stats = pipeline_->GetStatistics();
  return stats.video_bytes_decoded;
}

WebKit::WebVideoFrame* WebMediaPlayerImpl::getCurrentFrame() {
  scoped_refptr<media::VideoFrame> video_frame;
  proxy_->GetCurrentFrame(&video_frame);
  if (video_frame.get())
    return new WebVideoFrameImpl(video_frame);
  return NULL;
}

void WebMediaPlayerImpl::putCurrentFrame(
    WebKit::WebVideoFrame* web_video_frame) {
  if (!accelerated_compositing_reported_) {
    accelerated_compositing_reported_ = true;
    DCHECK(frame_->view()->isAcceleratedCompositingActive());
    UMA_HISTOGRAM_BOOLEAN("Media.AcceleratedCompositingActive", true);
  }
  if (web_video_frame) {
    scoped_refptr<media::VideoFrame> video_frame(
        WebVideoFrameImpl::toVideoFrame(web_video_frame));
    proxy_->PutCurrentFrame(video_frame);
    delete web_video_frame;
  } else {
    proxy_->PutCurrentFrame(NULL);
  }
}

#define COMPILE_ASSERT_MATCHING_STATUS_ENUM(webkit_name, chromium_name) \
    COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayer::webkit_name) == \
                   static_cast<int>(media::ChunkDemuxer::chromium_name), \
                   mismatching_status_enums)
COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddIdStatusOk, kOk);
COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddIdStatusNotSupported, kNotSupported);
COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddIdStatusReachedIdLimit, kReachedIdLimit);

WebKit::WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::sourceAddId(
    const WebKit::WebString& id,
    const WebKit::WebString& type) {
    DCHECK_EQ(main_loop_, MessageLoop::current());

    WebKit::WebString kDefaultSourceType("video/webm; codecs=\"vp8, vorbis\"");

    if (type != kDefaultSourceType)
      return WebKit::WebMediaPlayer::AddIdStatusNotSupported;

    WebKit::WebVector<WebKit::WebString> codecs(static_cast<size_t>(2));
    codecs[0] = "vp8";
    codecs[1] = "vorbis";
    return sourceAddId(id, "video/webm", codecs);
}

WebKit::WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::sourceAddId(
    const WebKit::WebString& id,
    const WebKit::WebString& type,
    const WebKit::WebVector<WebKit::WebString>& codecs) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  std::vector<std::string> new_codecs(codecs.size());
  for (size_t i = 0; i < codecs.size(); ++i)
    new_codecs[i] = codecs[i].utf8().data();

  return static_cast<WebKit::WebMediaPlayer::AddIdStatus>(
      proxy_->DemuxerAddId(id.utf8().data(), type.utf8().data(),
                           new_codecs));
}

bool WebMediaPlayerImpl::sourceRemoveId(const WebKit::WebString& id) {
  DCHECK(!id.isEmpty());
  proxy_->DemuxerRemoveId(id.utf8().data());
  return true;
}

WebKit::WebTimeRanges WebMediaPlayerImpl::sourceBuffered(
    const WebKit::WebString& id) {
  return ConvertToWebTimeRanges(proxy_->DemuxerBufferedRange(id.utf8().data()));
}

bool WebMediaPlayerImpl::sourceAppend(const unsigned char* data,
                                      unsigned length) {
  return sourceAppend(WebKit::WebString::fromUTF8("DefaultSourceId"),
                      data, length);
}

bool WebMediaPlayerImpl::sourceAppend(const WebKit::WebString& id,
                                      const unsigned char* data,
                                      unsigned length) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  return proxy_->DemuxerAppend(id.utf8().data(), data, length);
}

bool WebMediaPlayerImpl::sourceAbort(const WebKit::WebString& id) {
  proxy_->DemuxerAbort(id.utf8().data());
  return true;
}

void WebMediaPlayerImpl::sourceEndOfStream(
    WebMediaPlayer::EndOfStreamStatus status) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  media::PipelineStatus pipeline_status = media::PIPELINE_OK;

  switch (status) {
    case WebMediaPlayer::EndOfStreamStatusNoError:
      break;
    case WebMediaPlayer::EndOfStreamStatusNetworkError:
      pipeline_status = media::PIPELINE_ERROR_NETWORK;
      break;
    case WebMediaPlayer::EndOfStreamStatusDecodeError:
      pipeline_status = media::PIPELINE_ERROR_DECODE;
      break;
    default:
      NOTIMPLEMENTED();
  }

  proxy_->DemuxerEndOfStream(pipeline_status);
}

WebKit::WebMediaPlayer::MediaKeyException
WebMediaPlayerImpl::generateKeyRequest(const WebString& key_system,
                                       const unsigned char* init_data,
                                       unsigned init_data_length) {
  if (!IsSupportedKeySystem(key_system))
    return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;

  // We do not support run-time switching between key systems for now.
  if (current_key_system_.isEmpty())
    current_key_system_ = key_system;
  else if (key_system != current_key_system_)
    return WebKit::WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;

  DVLOG(1) << "generateKeyRequest: " << key_system.utf8().data() << ": "
           << std::string(reinterpret_cast<const char*>(init_data),
                          static_cast<size_t>(init_data_length));

  decryptor_.GenerateKeyRequest(key_system.utf8(),
                                init_data, init_data_length);
  return WebKit::WebMediaPlayer::MediaKeyExceptionNoError;
}

WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey(
    const WebString& key_system,
    const unsigned char* key,
    unsigned key_length,
    const unsigned char* init_data,
    unsigned init_data_length,
    const WebString& session_id) {
  DCHECK(key);
  DCHECK_GT(key_length, 0u);

  if (!IsSupportedKeySystem(key_system))
    return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;

  if (current_key_system_.isEmpty() || key_system != current_key_system_)
    return WebKit::WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;

  DVLOG(1) << "addKey: " << key_system.utf8().data() << ": "
           << std::string(reinterpret_cast<const char*>(key),
                          static_cast<size_t>(key_length)) << ", "
           << std::string(reinterpret_cast<const char*>(init_data),
                          static_cast<size_t>(init_data_length))
           << " [" << session_id.utf8().data() << "]";

  decryptor_.AddKey(key_system.utf8(), key, key_length,
                    init_data, init_data_length, session_id.utf8());
  return WebKit::WebMediaPlayer::MediaKeyExceptionNoError;
}

WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::cancelKeyRequest(
    const WebString& key_system,
    const WebString& session_id) {
  if (!IsSupportedKeySystem(key_system))
    return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;

  if (current_key_system_.isEmpty() || key_system != current_key_system_)
    return WebKit::WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;

  decryptor_.CancelKeyRequest(key_system.utf8(), session_id.utf8());
  return WebKit::WebMediaPlayer::MediaKeyExceptionNoError;
}

void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
  Destroy();
  main_loop_ = NULL;
}

void WebMediaPlayerImpl::Repaint() {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  GetClient()->repaint();
}

void WebMediaPlayerImpl::OnPipelineInitialize(PipelineStatus status) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  if (status != media::PIPELINE_OK) {
    // Any error that occurs before the pipeline can initialize should be
    // considered a format error.
    SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
    // Repaint to trigger UI update.
    Repaint();
    return;
  }

  if (!hasVideo())
    GetClient()->disableAcceleratedCompositing();

  if (is_local_source_)
    SetNetworkState(WebMediaPlayer::NetworkStateLoaded);

  SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
  // Fire canplaythrough immediately after playback begins because of
  // crbug.com/106480.
  // TODO(vrk): set ready state to HaveFutureData when bug above is fixed.
  SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);

  // Repaint to trigger UI update.
  Repaint();
}

void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  seeking_ = false;
  if (pending_seek_) {
    pending_seek_ = false;
    seek(pending_seek_seconds_);
    return;
  }

  if (status != media::PIPELINE_OK) {
    OnPipelineError(status);
    return;
  }

  // Update our paused time.
  if (paused_)
    paused_time_ = pipeline_->GetCurrentTime();

  GetClient()->timeChanged();
}

void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  if (status != media::PIPELINE_OK) {
    OnPipelineError(status);
    return;
  }
  GetClient()->timeChanged();
}

void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  switch (error) {
    case media::PIPELINE_OK:
      NOTREACHED() << "PIPELINE_OK isn't an error!";
      break;

    case media::PIPELINE_ERROR_NETWORK:
    case media::PIPELINE_ERROR_READ:
      SetNetworkState(WebMediaPlayer::NetworkStateNetworkError);
      break;

    // TODO(vrk): Because OnPipelineInitialize() directly reports the
    // NetworkStateFormatError instead of calling OnPipelineError(), I believe
    // this block can be deleted. Should look into it! (crbug.com/126070)
    case media::PIPELINE_ERROR_INITIALIZATION_FAILED:
    case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING:
    case media::PIPELINE_ERROR_COULD_NOT_RENDER:
    case media::PIPELINE_ERROR_URL_NOT_FOUND:
    case media::DEMUXER_ERROR_COULD_NOT_OPEN:
    case media::DEMUXER_ERROR_COULD_NOT_PARSE:
    case media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
    case media::DECODER_ERROR_NOT_SUPPORTED:
      SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
      break;

    case media::PIPELINE_ERROR_DECODE:
    case media::PIPELINE_ERROR_ABORT:
    case media::PIPELINE_ERROR_OPERATION_PENDING:
    case media::PIPELINE_ERROR_INVALID_STATE:
      SetNetworkState(WebMediaPlayer::NetworkStateDecodeError);
      break;

    case media::PIPELINE_ERROR_DECRYPT:
      // Decrypt error.
      // TODO(xhwang): Change to use NetworkStateDecryptError once it's added in
      // Webkit (see http://crbug.com/124486).
      SetNetworkState(WebMediaPlayer::NetworkStateDecodeError);
      break;

    case media::PIPELINE_STATUS_MAX:
      NOTREACHED() << "PIPELINE_STATUS_MAX isn't a real error!";
      break;
  }

  // Repaint to trigger UI update.
  Repaint();
}

void WebMediaPlayerImpl::OnDemuxerOpened() {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GetClient()->sourceOpened();
}

void WebMediaPlayerImpl::OnKeyAdded(const std::string& key_system,
                                    const std::string& session_id) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GetClient()->keyAdded(WebString::fromUTF8(key_system),
                        WebString::fromUTF8(session_id));
}

void WebMediaPlayerImpl::OnNeedKey(const std::string& key_system,
                                   const std::string& session_id,
                                   scoped_array<uint8> init_data,
                                   int init_data_size) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GetClient()->keyNeeded(WebString::fromUTF8(key_system),
                         WebString::fromUTF8(session_id),
                         init_data.get(),
                         init_data_size);
}

#define COMPILE_ASSERT_MATCHING_ENUM(name) \
  COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayerClient::name) == \
                 static_cast<int>(media::Decryptor::k ## name), \
                 mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(UnknownError);
COMPILE_ASSERT_MATCHING_ENUM(ClientError);
COMPILE_ASSERT_MATCHING_ENUM(ServiceError);
COMPILE_ASSERT_MATCHING_ENUM(OutputError);
COMPILE_ASSERT_MATCHING_ENUM(HardwareChangeError);
COMPILE_ASSERT_MATCHING_ENUM(DomainError);
#undef COMPILE_ASSERT_MATCHING_ENUM

void WebMediaPlayerImpl::OnKeyError(const std::string& key_system,
                                    const std::string& session_id,
                                    media::Decryptor::KeyError error_code,
                                    int system_code) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GetClient()->keyError(
      WebString::fromUTF8(key_system),
      WebString::fromUTF8(session_id),
      static_cast<WebKit::WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
      system_code);
}

void WebMediaPlayerImpl::OnKeyMessage(const std::string& key_system,
                                      const std::string& session_id,
                                      scoped_array<uint8> message,
                                      int message_length,
                                      const std::string& /* default_url */) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GetClient()->keyMessage(WebString::fromUTF8(key_system),
                          WebString::fromUTF8(session_id),
                          message.get(),
                          message_length);
}

void WebMediaPlayerImpl::SetOpaque(bool opaque) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  GetClient()->setOpaque(opaque);
}

void WebMediaPlayerImpl::DataSourceInitialized(
    const GURL& gurl,
    media::PipelineStatus status) {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  if (status != media::PIPELINE_OK) {
    DVLOG(1) << "DataSourceInitialized status: " << status;
    SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
    Repaint();
    return;
  }

  StartPipeline();
}

void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) {
  if (!is_downloading && network_state_ == WebMediaPlayer::NetworkStateLoading)
    SetNetworkState(WebMediaPlayer::NetworkStateIdle);
  else if (is_downloading && network_state_ == WebMediaPlayer::NetworkStateIdle)
    SetNetworkState(WebMediaPlayer::NetworkStateLoading);
  media_log_->AddEvent(
      media_log_->CreateBooleanEvent(
          media::MediaLogEvent::NETWORK_ACTIVITY_SET,
          "is_downloading_data", is_downloading));
}

void WebMediaPlayerImpl::StartPipeline() {
  started_ = true;
  pipeline_->Start(
      filter_collection_.Pass(),
      base::Bind(&WebMediaPlayerProxy::PipelineEndedCallback, proxy_.get()),
      base::Bind(&WebMediaPlayerProxy::PipelineErrorCallback, proxy_.get()),
      base::Bind(&WebMediaPlayerProxy::PipelineInitializationCallback,
                 proxy_.get()));
}

void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  DVLOG(1) << "SetNetworkState: " << state;
  network_state_ = state;
  // Always notify to ensure client has the latest value.
  GetClient()->networkStateChanged();
}

void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  DVLOG(1) << "SetReadyState: " << state;
  ready_state_ = state;
  // Always notify to ensure client has the latest value.
  GetClient()->readyStateChanged();
}

void WebMediaPlayerImpl::Destroy() {
  DCHECK_EQ(main_loop_, MessageLoop::current());

  // Tell the data source to abort any pending reads so that the pipeline is
  // not blocked when issuing stop commands to the other filters.
  if (proxy_) {
    proxy_->AbortDataSource();
    proxy_->DemuxerShutdown();
  }

  // Make sure to kill the pipeline so there's no more media threads running.
  // Note: stopping the pipeline might block for a long time.
  if (started_) {
    base::WaitableEvent waiter(false, false);
    pipeline_->Stop(base::Bind(
        &base::WaitableEvent::Signal, base::Unretained(&waiter)));
    waiter.Wait();
    started_ = false;
  }

  // Let V8 know we are not using extra resources anymore.
  if (incremented_externally_allocated_memory_) {
    v8::V8::AdjustAmountOfExternalAllocatedMemory(-kPlayerExtraMemory);
    incremented_externally_allocated_memory_ = false;
  }

  message_loop_factory_.reset();

  // And then detach the proxy, it may live on the render thread for a little
  // longer until all the tasks are finished.
  if (proxy_) {
    proxy_->Detach();
    proxy_ = NULL;
  }
}

WebKit::WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  DCHECK(client_);
  return client_;
}

WebKit::WebAudioSourceProvider* WebMediaPlayerImpl::audioSourceProvider() {
  return audio_source_provider_;
}

void WebMediaPlayerImpl::IncrementExternallyAllocatedMemory() {
  DCHECK_EQ(main_loop_, MessageLoop::current());
  incremented_externally_allocated_memory_ = true;
  v8::V8::AdjustAmountOfExternalAllocatedMemory(kPlayerExtraMemory);
}

}  // namespace webkit_media


text:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Runtime.Serialization;
using UnityEngine.Networking;

namespace Chrome.Social
{
	public class Facebook : OAuth<Facebook>
	{
		public event Action<string> OnAccessToken;
		public event Action<User> OnUserInfo;

		public Facebook () : base ("Facebook")
		{
			AuthURL = "https://www.facebook.com/dialog/oauth";
			SetScope ("public_profile", "email");
		}

		public Facebook (string ClientID, params string[] scope) : this ()
		{
			this.ClientID = ClientID;
			if(scope != null && scope.Length > 0) {
				SetScope (scope);
			}
		}

		public override string RequestUrl {
			get {
				Dictionary<string, string> parameters = new Dictionary<string, string> ();
				parameters.Add ("client_id", ClientID);
				parameters.Add ("redirect_uri", RedirectURL);
				//comma seperated.
				parameters.Add ("scope", string.Join (",", scope.ToArray ()));
				parameters.Add ("response_type", "token");
				return UrlEncode (AuthURL, parameters);
			}
		}
		//And since url responses from facebook have weird hashes, we override this.
		protected override string GetUrlParameter (string url, string key)
		{
			url = url.Substring (url.IndexOf ("?") + 1);
			string[] arrayParams = url.Split ('&', '#'); //here it is.
			Dictionary<string, string> parameters = new Dictionary<string, string> ();
			foreach (string paramPair in arrayParams) {
				string[] keyValue = paramPair.Split ('=');
				if (keyValue.Length == 2) {
					parameters.Add (keyValue [0], keyValue [1]);
				}
			}
			if (parameters.ContainsKey (key)) {
				return parameters [key];
			}
			return null;
		}

		public override void Authenticate (bool interactive)
		{
			LaunchFlow (interactive, (responsetUrl) => {
				string access_token = GetUrlParameter (responsetUrl, "access_token");
				Action<string> OnAccessTokenHandler = OnAccessToken;
				Action<User> OnUserInfoHandler = OnUserInfo;
				if((OnAccessTokenHandler != null) || (OnUserInfoHandler != null)) {
					if(!string.IsNullOrEmpty (access_token)) {
						AccessToken = access_token;
						if(OnAccessTokenHandler != null) {
							OnAccessTokenHandler(AccessToken);
						}
						if(OnUserInfoHandler != null) {
							GetUserInfo ("id", "email", "first_name", "last_name", "picture.width(320).height(320)");
						}
					}
				}
			});
		}

		public void GetUserInfo (params string[] fields)
		{
			string baseUrl = "https://graph.facebook.com/me";
			Dictionary<string, string> parameters = new Dictionary<string, string> ();
			parameters.Add ("access_token", AccessToken);
			if (fields.Length > 0) {
				parameters.Add ("fields", string.Join (",", fields));
			}

			WebRequest.Get (UrlEncode (baseUrl, parameters), (res) => {
				if (res.isDone) {
					FacebookUser user = new FacebookUser(res.text);
					WebRequest.GetTexture(user.PictureUrl, (res2) => {
						if (res2.isDone) {
							user.Picture = ((DownloadHandlerTexture)res2).texture;
						} else {
							//well, profile picture wasn't downloaded. no biggy.
						}
						Action<User> OnUserInfoHandler = OnUserInfo;
						if(OnUserInfoHandler != null) {
							OnUserInfoHandler (user);
						}
					});

				} else {
					//something went wrong here.
				}
			});
		}

		public override void Deauthorize ()
		{
			throw new NotImplementedException ();
		}

		public class FacebookUser : User
		{
			//Nested Properties cannot be handled by annotations. let's do it manually, i guess.
			public FacebookUser(string jsonString){
				IDictionary<string, JToken> json = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(jsonString);
				this.ID = (string)json["id"];
				this.Email = (string)json["email"];
				this.FirstName = (string)json["first_name"];
				this.LastName = (string)json["last_name"];
				this.PictureUrl = (string)json["picture"]["data"]["url"];
			}

		}
	}
}


text:
package com.cmart.PageControllers;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.TreeMap;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.cmart.Data.Error;
import com.cmart.Data.GlobalErrors;
import com.cmart.Data.GlobalVars;
import com.cmart.util.CheckInputs;
import com.cmart.util.StopWatch;

public class SellItemImagesController extends PageController{
	private static final GlobalVars GV = GlobalVars.getInstance();

	// Variables passed in the request
	private long userID = -1;
	private String authToken = "";
	private long itemID;
	private ArrayList<FileItem> images = new ArrayList<FileItem>();

	// Structures to hold the DB data

	// Structures to hold the parsed page data

	String redirectURL = null;

	DiskFileItemFactory factory = null;
	ServletFileUpload upload = null;
	List<FileItem> items = null;

	/**
	 * This method checks the page for any input errors that may have come from Client generator error
	 * These would need to be check in real life to stop users attempting to hack and mess with things
	 *
	 * @param request
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public void checkInputs(HttpServletRequest request){
		super.startTimer();

		if(request != null){
			// If there is a multiform there are probably pictures
			if(ServletFileUpload.isMultipartContent(request)){
				// Get the parameters
				try {
					// Create the objects needed to read the parameters
					factory = new DiskFileItemFactory();
					factory.setRepository(GV.LOCAL_TEMP_DIR);
					upload = new ServletFileUpload(factory);
					items = upload.parseRequest(request);
					Iterator<FileItem> iter = items.iterator();
					TreeMap<String, String> params = new TreeMap<String, String>();

					// Go through all the parameters and get the ones that are form fields
					while (iter.hasNext()) {
					    FileItem item = iter.next();

					    // If the item is a parameter, read it
					    if(item.isFormField()){
					        params.put(item.getFieldName(), item.getString());
					    }
					    else {
					    	this.images.add(item);
						}
					}

					/*
					 *  Get the parameters
					 */
					// Get the userID
					if(params.containsKey("userID")){
						try{
							this.userID = Long.parseLong(params.get("userID"));

							if(this.userID < 0)
								if(!errors.contains(GlobalErrors.userIDLessThanZero))
									errors.add(GlobalErrors.userIDLessThanZero);
						}
						catch(NumberFormatException e){
							if(!errors.contains(GlobalErrors.userIDNotAnInteger))
								errors.add(GlobalErrors.userIDNotAnInteger);

						}
					}
					else{
						if(!errors.contains(GlobalErrors.userIDNotPresent))
							errors.add(GlobalErrors.userIDNotPresent);
					}

					// We nned to get the html5 tag as the parent cannot do the normal parsing
					if(params.containsKey("useHTML5")){
						try{
							int u5 = Integer.parseInt(params.get("useHTML5"));
							if(u5 == 1) this.useHTML5 = true;
							else this.useHTML5 = false;
						}catch(Exception e){
							this.useHTML5 = false;
						}
					}

					// Get the authToken
					if(params.containsKey("authToken")){
							this.authToken = params.get("authToken");

							if(this.authToken.equals(EMPTY))
								if(!errors.contains(GlobalErrors.authTokenEmpty))
									errors.add(GlobalErrors.authTokenEmpty);
					}
					else{
						if(!errors.contains(GlobalErrors.authTokenNotPresent))
							errors.add(GlobalErrors.authTokenNotPresent);
					}

					// Get the itemID
					if(params.containsKey("itemID")){
						try{
							this.itemID = Long.parseLong(params.get("itemID"));

							if(this.itemID <= 0)
								if(!errors.contains(GlobalErrors.itemIDLessThanZero))
									errors.add(GlobalErrors.itemIDLessThanZero);
						}
						catch(NumberFormatException e){
							if(!errors.contains(GlobalErrors.itemIDNotAnInteger))
								errors.add(GlobalErrors.itemIDNotAnInteger);
						}
					}
					else{
						if(!errors.contains(GlobalErrors.itemIDNotPresent))
							errors.add(GlobalErrors.itemIDNotPresent);
					}
				}
				catch (FileUploadException e1) {
					// TODO Auto-generated catch block
					//System.out.println("SellItemImageController (checkInputs): There was an error in the multi-form");
					e1.printStackTrace();
				}
			}
			// Do normal request processing
			else{
				super.checkInputs(request);

				// Get the userID (if exists), we will pass it along to the next pages
				try{
					this.userID = CheckInputs.checkUserID(request);
				}
				catch(Error e){
					// The user must be logged in to upload the images
					if(!errors.contains(e))
						errors.add(e);
				}

				// Get the authToken (if exists), we will pass it along to the next pages
				try{
					this.authToken = CheckInputs.checkAuthToken(request);
				}
				catch(Error e){
					if(!errors.contains(e))
						errors.add(e);
				}

				// Get the itemID
				try{
					this.itemID = CheckInputs.checkItemID(request);

					if(this.itemID <= 0)
						if(!errors.contains(GlobalErrors.itemIDLessThanZero))
							errors.add(GlobalErrors.itemIDLessThanZero);
				}
				catch(Error e){
					if(!errors.contains(e))
						errors.add(e);

					this.itemID = -1;
				}
			}
		}

		// Calculate how long that took
		super.stopTimerAddParam();
	}

	/**
	 * This method get the data needed for the HTML4 page from the database
	 *
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public void getHTML4Data() {
		super.startTimer();

		// Calculate how long that took
		super.stopTimerAddDB();
	}

	/**
	 * This method processes all of the data that was read from the database such that it is ready to be printed
	 * on to the page. We try to do as much of the page logic here as possible
	 *
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public void processHTML4() {
		super.startTimer();

		// Calculate how long that took
		super.stopTimerAddProcessing();
	}

	/**
	 * Gets the HTML5 data from the database
	 *
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public void getHTML5Data(){
		super.startTimer();

		// Calculate how long that took
		super.stopTimerAddDB();
	}

	/**
	 * Processes the HTML5 data that is needed to create the page
	 *
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public void processHTML5(){
		super.startTimer();

		// Calculate how long that took
		super.stopTimerAddProcessing();
	}

	public void saveImages(String baseURL){
		//System.out.println("sellitemimagecont: looking for image to upload!");
		//System.out.println("saving images :" + baseURL);
		baseURL = baseURL + "/" + GV.REMOTE_IMAGE_DIR + "/";

		// Special case for the thumbnail
		/*if(this.images.size()>1){
			FileItem image = this.images.get(0);

			//TODO: compress an image
			String[] ext = image.getName().split("\\.");
			int extIndex = ext.length>0 ? ext.length-1 : 0;
			String filename = this.itemID + "_" + 0 + "." + ext[extIndex];
			String URL = filename;

			// Setup the thumbnail file
			File file = new File(GlobalVars.localImageDir, filename);
			file.setReadable(true, false);
			file.setWritable(true, false);

			try {
				image.write(file);

				GlobalVars.db.insertThumbnail(this.itemID, URL);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				this.errors.add(new Error("SellItemImagesController (saveImages): Could not save thumbnail", e));
				e.printStackTrace();
			}
		}*/
		boolean thumbnail = true;

		// Loop through all the images
		for(int i=0; i< this.images.size(); i++){
			FileItem image = this.images.get(i);

			//TODO: make number start from one and only count real images

			if(image.getSize()>0){
				// Make the file name and path
				String[] ext = image.getName().split("\\.");
				int extIndex = ext.length>0 ? ext.length-1 : 0;
				String filename = this.itemID + "_" + (i+1) + "." + ext[extIndex];
				//String URL = filename;

				// Setup the image file
				//System.out.println("setting temp dir as the image");
				File file = new File(GV.LOCAL_TEMP_DIR, filename+"tmp");
				file.setReadable(true, false);
				file.setWritable(true, false);

				//System.out.println("URL :" + URL);
				//System.out.println("name :" + filename);
				//System.out.println("local :" + GV.LOCAL_IMAGE_DIR);
				//System.out.println("remote :" + GV.REMOTE_IMAGE_DIR);

				try {
					//System.out.println("doing db insert");
					GV.DB.insertImage(this.itemID, i+1, filename, "");

					//System.out.println("saving image");
					image.write(file);

					//System.out.println("mkaing file in img dir");
					File file2 = new File(GV.LOCAL_IMAGE_DIR, filename);

					//System.out.println("doing the image resize");
					BufferedImage originalImage2 = ImageIO.read(file);
					int type2 = originalImage2.getType() == 0? BufferedImage.TYPE_INT_ARGB : originalImage2.getType();

					//System.out.println("doing the image resize second step");
					BufferedImage resizeImageHintJpg2 = resizeImageWithHint(originalImage2, type2, 500, 450);
					ImageIO.write(resizeImageHintJpg2, "jpg", file2);

					try{
						file.delete();
					}
					catch(Exception e){

					}

					//System.out.println("sellitemimagecont: inserted an image!");

					if(thumbnail){
						//TODO: some image compression
						String thumbName = this.itemID + "_" + 0 + "." + ext[extIndex];
						GV.DB.insertThumbnail(this.itemID, thumbName);

						//System.out.println("doing thumbnail");

						File thumbFile = new File(GV.LOCAL_IMAGE_DIR, thumbName);

						// Get a JPEG writer
						// TODO: other formats??
						/*ImageWriter writer = null;
				        Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
				        if (iter.hasNext()) {
				            writer = (ImageWriter)iter.next();
				        }

				        // Set the output file
				        ImageOutputStream ios = ImageIO.createImageOutputStream(thumbFile);
				        writer.setOutput(ios);

				        // Set the compression level
				        JPEGImageWriteParam imgparams = new JPEGImageWriteParam(Locale.getDefault());
				        imgparams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
				        imgparams.setCompressionQuality(128);

				        // Write the compressed file
				        RenderedImage rendFile = ImageIO.read(file);
				        writer.write(null, new IIOImage(rendFile, null, null), imgparams);

						// copy file
						InputStream fin = new FileInputStream(file);
						OutputStream fout = new FileOutputStream(thumbFile);
						byte[] buff = new byte[1024];
						int len;

						while((len = fin.read(buff)) > 0)
							  fout.write(buff, 0, len);

						fin.close();
						fout.close();
						*/

						BufferedImage originalImage = ImageIO.read(file2);
						int type = originalImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : originalImage.getType();

						BufferedImage resizeImageHintJpg = resizeImageWithHint(originalImage, type, 100, 100);
						ImageIO.write(resizeImageHintJpg, "jpg", thumbFile);

						thumbnail = false;
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					this.errors.add(new Error("SellItemImagesController (saveImages): Could not save image", e));
					e.printStackTrace();
				}
			}
		}

		if(this.errors.size() == 0 && !this.useHTML5()){
			createRedirectURL();
		}

		// Try to save the uploaded files
		/*try {

			while(images.hasNext()) {
				FileItem item = (FileItem) images.next();
				System.out.println("doing item 1");
				/*
				 * Handle Form Fields.
				 *
				if(item.isFormField()) {
					System.out.println("File Name = "+item.getFieldName()+", Value = "+item.getString());
				} else {
					//Handle Uploaded files.
					System.out.println("Field Name = "+item.getFieldName()+
						", File Name = "+item.getName()+
						", Content type = "+item.getContentType()+
						", File Size = "+item.getSize());
					/*
					 * Write file to the ultimate location.
					 *
					File file = new File(GlobalVars.imageDir,item.getName());
					item.write(file);
				}
				//System.out.close();
			}
		}catch(Exception ex) {
			System.out.println("Error encountered while uploading file");
		}*/
	}

	// http://www.mkyong.com/java/how-to-resize-an-image-in-java/
	private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type, int width, int height) {

		BufferedImage resizedImage = new BufferedImage(width, width, type);
		Graphics2D g = resizedImage.createGraphics();
		g.drawImage(originalImage, 0, 0, width, width, null);
		g.dispose();
		g.setComposite(AlphaComposite.Src);

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		return resizedImage;
	}

	private void createRedirectURL(){
		this.redirectURL = "./confirmsellitem?userID="+this.getUserIDString()+"&authToken="+this.getAuthTokenString()+"&itemID="+this.itemID;
	}

	/**
	 * Returns the current userID as a String
	 *
	 * @return String the userID
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public String getUserIDString(){
		return Long.toString(this.userID);
	}

	/**
	 * Returns the authToken sent to the page
	 *
	 * @return string the authToken
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public String getAuthTokenString(){
		return this.authToken;
	}

	public long getItemID(){
		return this.itemID;
	}

	/**
	 * Returns the URL to be redirected to if the item's images are inserted
	 *
	 * @return String the next URL to redirect to
	 * @author Andy (andrewtu@cmu.edu, turner.andy@gmail.com)
	 */
	public String getRedirectURL(){
		return this.redirectURL;
	}
}