text:
package object

import (
	"bytes"
	"context"
	cryptorand "crypto/rand"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"runtime"
	"runtime/debug"
	"sync"
	"testing"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/require"
	"golang.org/x/sync/errgroup"

	"github.com/kopia/kopia/internal/clock"
	"github.com/kopia/kopia/internal/gather"
	"github.com/kopia/kopia/internal/impossible"
	"github.com/kopia/kopia/internal/testlogging"
	"github.com/kopia/kopia/internal/testutil"
	"github.com/kopia/kopia/repo/blob"
	"github.com/kopia/kopia/repo/compression"
	"github.com/kopia/kopia/repo/content"
	"github.com/kopia/kopia/repo/format"
	"github.com/kopia/kopia/repo/splitter"
)

var errSomeError = errors.Errorf("some error")

type fakeContentManager struct {
	mu sync.Mutex

	// +checklocks:mu
	data map[content.ID][]byte
	// +checklocks:mu
	compresionIDs map[content.ID]compression.HeaderID

	supportsContentCompression bool
	writeContentError          error
}

func (f *fakeContentManager) PrefetchContents(ctx context.Context, contentIDs []content.ID, hint string) []content.ID {
	return contentIDs
}

func (f *fakeContentManager) GetContent(ctx context.Context, contentID content.ID) ([]byte, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	if d, ok := f.data[contentID]; ok {
		return append([]byte(nil), d...), nil
	}

	return nil, content.ErrContentNotFound
}

func (f *fakeContentManager) WriteContent(ctx context.Context, data gather.Bytes, prefix content.IDPrefix, comp compression.HeaderID) (content.ID, error) {
	if f.writeContentError != nil {
		return content.EmptyID, f.writeContentError
	}

	h := sha256.New()
	data.WriteTo(h)
	contentID, err := content.IDFromHash(prefix, h.Sum(nil))
	impossible.PanicOnError(err)

	f.mu.Lock()
	defer f.mu.Unlock()

	f.data[contentID] = data.ToByteSlice()
	if f.compresionIDs != nil {
		f.compresionIDs[contentID] = comp
	}

	return contentID, nil
}

func (f *fakeContentManager) SupportsContentCompression() (bool, error) {
	return f.supportsContentCompression, nil
}

func (f *fakeContentManager) ContentInfo(ctx context.Context, contentID content.ID) (content.Info, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	if d, ok := f.data[contentID]; ok {
		return &content.InfoStruct{ContentID: contentID, PackedLength: uint32(len(d))}, nil
	}

	return nil, blob.ErrBlobNotFound
}

func (f *fakeContentManager) Flush(ctx context.Context) error {
	return nil
}

func setupTest(t *testing.T, compressionHeaderID map[content.ID]compression.HeaderID) (map[content.ID][]byte, *fakeContentManager, *Manager) {
	t.Helper()

	data := map[content.ID][]byte{}

	fcm := &fakeContentManager{
		data:                       data,
		supportsContentCompression: compressionHeaderID != nil,
		compresionIDs:              compressionHeaderID,
	}

	r, err := NewObjectManager(testlogging.Context(t), fcm, format.ObjectFormat{
		Splitter: "FIXED-1M",
	}, nil)
	if err != nil {
		t.Fatalf("can't create object manager: %v", err)
	}

	return data, fcm, r
}

func TestWriters(t *testing.T) {
	ctx := testlogging.Context(t)
	cases := []struct {
		data     []byte
		objectID ID
	}{
		{
			[]byte("the quick brown fox jumps over the lazy dog"),
			mustParseID(t, "05c6e08f1d9fdafa03147fcb8f82f124c76d2f70e3d989dc8aadb5e7d7450bec"),
		},
		{make([]byte, 100), mustParseID(t, "cd00e292c5970d3c5e2f0ffa5171e555bc46bfc4faddfb4a418b6840b86e79a3")}, // 100 zero bytes
	}

	for _, c := range cases {
		data, _, om := setupTest(t, nil)

		writer := om.NewWriter(ctx, WriterOptions{})

		if _, err := writer.Write(c.data); err != nil {
			t.Errorf("write error: %v", err)
		}

		result, err := writer.Result()
		if err != nil {
			t.Errorf("error getting writer results for %v, expected: %v", c.data, c.objectID.String())
			continue
		}

		if !objectIDsEqual(result, c.objectID) {
			t.Errorf("incorrect result for %v, expected: %v got: %v", c.data, c.objectID.String(), result.String())
		}

		if _, _, ok := c.objectID.ContentID(); !ok {
			if len(data) != 0 {
				t.Errorf("unexpected data written to the storage: %v", data)
			}
		} else {
			if len(data) != 1 {
				// 1 data block
				t.Errorf("unexpected data written to the storage: %v", data)
			}
		}
	}
}

func objectIDsEqual(o1, o2 ID) bool {
	return o1 == o2
}

func TestCompression_ContentCompressionEnabled(t *testing.T) {
	ctx := testlogging.Context(t)

	cmap := map[content.ID]compression.HeaderID{}
	_, _, om := setupTest(t, cmap)

	w := om.NewWriter(ctx, WriterOptions{
		Compressor: "gzip",
	})
	w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1000))
	oid, err := w.Result()
	require.NoError(t, err)

	cid, isCompressed, ok := oid.ContentID()
	require.True(t, ok)
	require.False(t, isCompressed) // oid will not indicate compression
	require.Equal(t, compression.ByName["gzip"].HeaderID(), cmap[cid])
}

func TestCompression_ContentCompressionDisabled(t *testing.T) {
	ctx := testlogging.Context(t)

	// this disables content compression
	_, _, om := setupTest(t, nil)

	w := om.NewWriter(ctx, WriterOptions{
		Compressor: "gzip",
	})
	w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1000))
	oid, err := w.Result()
	require.NoError(t, err)

	_, isCompressed, ok := oid.ContentID()
	require.True(t, ok)
	require.True(t, isCompressed) // oid will indicate compression
}

func TestWriterCompleteChunkInTwoWrites(t *testing.T) {
	ctx := testlogging.Context(t)
	_, _, om := setupTest(t, nil)

	b := make([]byte, 100)
	writer := om.NewWriter(ctx, WriterOptions{})
	writer.Write(b[0:50])
	writer.Write(b[0:50])
	result, err := writer.Result()

	if !objectIDsEqual(result, mustParseID(t, "cd00e292c5970d3c5e2f0ffa5171e555bc46bfc4faddfb4a418b6840b86e79a3")) {
		t.Errorf("unexpected result: %v err: %v", result, err)
	}
}

func TestCheckpointing(t *testing.T) {
	ctx := testlogging.Context(t)
	_, _, om := setupTest(t, nil)

	writer := om.NewWriter(ctx, WriterOptions{})

	// write all zeroes
	allZeroes := make([]byte, 1<<20)

	// empty file, nothing flushed
	checkpoint1, err := writer.Checkpoint()
	verifyNoError(t, err)

	// write some bytes, but not enough to flush.
	writer.Write(allZeroes[0:50])
	checkpoint2, err := writer.Checkpoint()
	verifyNoError(t, err)

	// write enough to flush first content.
	writer.Write(allZeroes)
	checkpoint3, err := writer.Checkpoint()
	verifyNoError(t, err)

	// write enough to flush second content.
	writer.Write(allZeroes)
	checkpoint4, err := writer.Checkpoint()
	verifyNoError(t, err)

	result, err := writer.Result()
	verifyNoError(t, err)

	if !objectIDsEqual(checkpoint1, EmptyID) {
		t.Errorf("unexpected checkpoint1: %v err: %v", checkpoint1, err)
	}

	if !objectIDsEqual(checkpoint2, EmptyID) {
		t.Errorf("unexpected checkpoint2: %v err: %v", checkpoint2, err)
	}

	result2, err := writer.Checkpoint()
	verifyNoError(t, err)

	if result2 != result {
		t.Errorf("invalid checkpoint after result: %v vs %v", result2, result)
	}

	verifyFull(ctx, t, om, checkpoint3, allZeroes)
	verifyFull(ctx, t, om, checkpoint4, make([]byte, 2<<20))
	verifyFull(ctx, t, om, result, make([]byte, 2<<20+50))
}

func TestObjectWriterRaceBetweenCheckpointAndResult(t *testing.T) {
	rand.Seed(clock.Now().UnixNano())

	ctx := testlogging.Context(t)
	data := map[content.ID][]byte{}
	fcm := &fakeContentManager{
		data: data,
	}

	om, err := NewObjectManager(testlogging.Context(t), fcm, format.ObjectFormat{
		Splitter: "FIXED-1M",
	}, nil)
	if err != nil {
		t.Fatalf("can't create object manager: %v", err)
	}

	allZeroes := make([]byte, 1<<20-5)

	repeat := 100
	if testutil.ShouldReduceTestComplexity() {
		repeat = 5
	}

	for i := 0; i < repeat; i++ {
		w := om.NewWriter(ctx, WriterOptions{
			AsyncWrites: 1,
		})

		w.Write(allZeroes)
		w.Write(allZeroes)
		w.Write(allZeroes)

		var eg errgroup.Group

		eg.Go(func() error {
			_, rerr := w.Result()

			return rerr
		})

		eg.Go(func() error {
			cpID, cperr := w.Checkpoint()
			if cperr == nil && cpID != EmptyID {
				ids, verr := VerifyObject(ctx, om.contentMgr, cpID)
				if verr != nil {
					return errors.Wrapf(err, "Checkpoint() returned invalid object %v", cpID)
				}

				for _, id := range ids {
					if id == content.EmptyID {
						return errors.Errorf("checkpoint returned empty id")
					}
				}
			}

			return nil
		})

		if err := eg.Wait(); err != nil {
			t.Fatal(err)
		}
	}
}

func verifyFull(ctx context.Context, t *testing.T, om *Manager, oid ID, want []byte) {
	t.Helper()

	r, err := Open(ctx, om.contentMgr, oid)
	if err != nil {
		t.Fatalf("unable to open %v: %v", oid, err)
	}

	defer r.Close()

	data, err := io.ReadAll(r)
	if err != nil {
		t.Fatalf("unable to read all: %v", err)
	}

	if !bytes.Equal(data, want) {
		t.Fatalf("unexpected data read for %v", oid)
	}
}

func verifyNoError(t *testing.T, err error) {
	t.Helper()

	if err != nil {
		t.Fatal(err)
	}
}

func verifyIndirectBlock(ctx context.Context, t *testing.T, om *Manager, oid ID) {
	t.Helper()

	for indexContentID, isIndirect := oid.IndexObjectID(); isIndirect; indexContentID, isIndirect = indexContentID.IndexObjectID() {
		func() {
			if c, _, ok := indexContentID.ContentID(); ok {
				if !c.HasPrefix() {
					t.Errorf("expected base content ID to be prefixed, was %v", c)
				}
			}

			rd, err := Open(ctx, om.contentMgr, indexContentID)
			if err != nil {
				t.Errorf("unable to open %v: %v", oid.String(), err)
				return
			}
			defer rd.Close()

			var ind indirectObject
			if err := json.NewDecoder(rd).Decode(&ind); err != nil {
				t.Errorf("cannot parse indirect stream: %v", err)
			}
		}()
	}
}

func TestIndirection(t *testing.T) {
	ctx := testlogging.Context(t)

	splitterFactory := splitter.Fixed(1000)
	cases := []struct {
		dataLength          int
		expectedBlobCount   int
		expectedIndirection int
	}{
		{dataLength: 200, expectedBlobCount: 1, expectedIndirection: 0},
		{dataLength: 1000, expectedBlobCount: 1, expectedIndirection: 0},
		{dataLength: 1001, expectedBlobCount: 3, expectedIndirection: 1},
		// 1 blob of 1000 zeros, 1 blob of 5 zeros + 1 index blob
		{dataLength: 3005, expectedBlobCount: 3, expectedIndirection: 1},
		// 1 blob of 1000 zeros + 1 index blob
		{dataLength: 4000, expectedBlobCount: 2, expectedIndirection: 1},
		// 1 blob of 1000 zeros + 1 index blob
		{dataLength: 10000, expectedBlobCount: 2, expectedIndirection: 1},
	}

	for _, c := range cases {
		data, _, om := setupTest(t, nil)

		contentBytes := make([]byte, c.dataLength)

		writer := om.NewWriter(ctx, WriterOptions{})
		writer.(*objectWriter).splitter = splitterFactory()

		if _, err := writer.Write(contentBytes); err != nil {
			t.Errorf("write error: %v", err)
		}

		result, err := writer.Result()
		if err != nil {
			t.Errorf("error getting writer results: %v", err)
		}

		t.Logf("len %v got %v", len(contentBytes), result)

		if indirectionLevel(result) != c.expectedIndirection {
			t.Errorf("incorrect indirection level for size: %v: %v, expected %v", c.dataLength, indirectionLevel(result), c.expectedIndirection)
		}

		if got, want := len(data), c.expectedBlobCount; got != want {
			t.Errorf("unexpected blob count for %v: %v, expected %v", c.dataLength, got, want)
		}

		b, err := VerifyObject(ctx, om.contentMgr, result)
		if err != nil {
			t.Errorf("error verifying %q: %v", result, err)
		}

		if got, want := len(b), c.expectedBlobCount; got != want {
			t.Errorf("invalid blob count for %v, got %v, wanted %v", result, got, want)
		}

		verifyIndirectBlock(ctx, t, om, result)
	}
}

func indirectionLevel(oid ID) int {
	indexObjectID, ok := oid.IndexObjectID()
	if !ok {
		return 0
	}

	return 1 + indirectionLevel(indexObjectID)
}

func TestHMAC(t *testing.T) {
	ctx := testlogging.Context(t)
	c := bytes.Repeat([]byte{0xcd}, 50)

	_, _, om := setupTest(t, nil)

	w := om.NewWriter(ctx, WriterOptions{})
	w.Write(c)
	result, err := w.Result()

	if result.String() != "cad29ff89951a3c085c86cb7ed22b82b51f7bdfda24f932c7f9601f51d5975ba" {
		t.Errorf("unexpected result: %v err: %v", result.String(), err)
	}
}

//nolint:gocyclo
func TestConcatenate(t *testing.T) {
	ctx := testlogging.Context(t)
	_, _, om := setupTest(t, nil)

	phrase := []byte("hello world\n")
	phraseLength := len(phrase)
	shortRepeatCount := 17
	longRepeatCount := 999999

	emptyObject := mustWriteObject(t, om, nil, "")

	// short uncompressed object - <content>
	shortUncompressedOID := mustWriteObject(t, om, bytes.Repeat(phrase, shortRepeatCount), "")

	// long uncompressed object - Ix<content>
	longUncompressedOID := mustWriteObject(t, om, bytes.Repeat(phrase, longRepeatCount), "")

	// short compressed object - Z<content>
	shortCompressedOID := mustWriteObject(t, om, bytes.Repeat(phrase, shortRepeatCount), "pgzip")

	// long compressed object - Ix<content>
	longCompressedOID := mustWriteObject(t, om, bytes.Repeat(phrase, longRepeatCount), "pgzip")

	if _, compressed, ok := shortUncompressedOID.ContentID(); !ok || compressed {
		t.Errorf("invalid test assumption - shortUncompressedOID %v", shortUncompressedOID)
	}

	if _, isIndex := longUncompressedOID.IndexObjectID(); !isIndex {
		t.Errorf("invalid test assumption - longUncompressedOID %v", longUncompressedOID)
	}

	if _, compressed, ok := shortCompressedOID.ContentID(); !ok || !compressed {
		t.Errorf("invalid test assumption - shortCompressedOID %v", shortCompressedOID)
	}

	if _, isIndex := longCompressedOID.IndexObjectID(); !isIndex {
		t.Errorf("invalid test assumption - longCompressedOID %v", longCompressedOID)
	}

	shortLength := phraseLength * shortRepeatCount
	longLength := phraseLength * longRepeatCount

	cases := []struct {
		inputs     []ID
		wantLength int
	}{
		{[]ID{emptyObject}, 0},
		{[]ID{shortUncompressedOID}, shortLength},
		{[]ID{longUncompressedOID}, longLength},
		{[]ID{shortCompressedOID}, shortLength},
		{[]ID{longCompressedOID}, longLength},

		{[]ID{shortUncompressedOID, shortUncompressedOID}, 2 * shortLength},
		{[]ID{shortUncompressedOID, shortCompressedOID}, 2 * shortLength},
		{[]ID{emptyObject, longCompressedOID, shortCompressedOID, emptyObject, longCompressedOID, shortUncompressedOID, shortUncompressedOID, emptyObject, emptyObject}, 2*longLength + 3*shortLength},
	}

	for _, tc := range cases {
		concatenatedOID, err := om.Concatenate(ctx, tc.inputs)
		if err != nil {
			t.Fatal(err)
		}

		r, err := Open(ctx, om.contentMgr, concatenatedOID)
		if err != nil {
			t.Fatal(err)
		}

		gotLength := int(r.Length())
		r.Close()

		if gotLength != tc.wantLength {
			t.Errorf("invalid length for %v: %v, want %v", tc.inputs, gotLength, tc.wantLength)
		}

		b := make([]byte, len(phrase))

		// read the concatenated object in buffers the size of a single phrase, each buffer should be identical.
		for n, readerr := r.Read(b); ; n, readerr = r.Read(b) {
			if errors.Is(readerr, io.EOF) {
				break
			}

			if n != len(b) {
				t.Errorf("invalid length: %v", n)
			}

			if !bytes.Equal(b, phrase) {
				t.Errorf("invalid buffer: %v", n)
			}
		}

		if _, err = VerifyObject(ctx, om.contentMgr, concatenatedOID); err != nil {
			t.Fatalf("verify error: %v", err)
		}

		// make sure results of concatenation can be further concatenated.
		concatenated3OID, err := om.Concatenate(ctx, []ID{concatenatedOID, concatenatedOID, concatenatedOID})
		if err != nil {
			t.Fatal(err)
		}

		r, err = Open(ctx, om.contentMgr, concatenated3OID)
		if err != nil {
			t.Fatal(err)
		}

		gotLength = int(r.Length())
		r.Close()

		if gotLength != tc.wantLength*3 {
			t.Errorf("invalid twice-concatenated object length: %v, want %v", gotLength, tc.wantLength*3)
		}
	}
}

func mustWriteObject(t *testing.T, om *Manager, data []byte, compressor compression.Name) ID {
	t.Helper()

	w := om.NewWriter(testlogging.Context(t), WriterOptions{Compressor: compressor})
	defer w.Close()

	_, err := w.Write(data)
	if err != nil {
		t.Fatal(err)
	}

	oid, err := w.Result()
	if err != nil {
		t.Fatal(err)
	}

	return oid
}

func TestReader(t *testing.T) {
	ctx := testlogging.Context(t)
	data, _, om := setupTest(t, nil)

	storedPayload := []byte("foo\nbar")

	cid, err := content.ParseID("a76999788386641a3ec798554f1fe7e6")
	require.NoError(t, err)

	data[cid] = storedPayload

	cases := []struct {
		text    string
		payload []byte
	}{
		{"a76999788386641a3ec798554f1fe7e6", storedPayload},
	}

	for _, c := range cases {
		objectID, err := ParseID(c.text)
		if err != nil {
			t.Errorf("cannot parse object ID: %v", err)
			continue
		}

		reader, err := Open(ctx, om.contentMgr, objectID)
		if err != nil {
			t.Errorf("cannot create reader for %v: %v", objectID, err)
			continue
		}

		d, err := io.ReadAll(reader)
		if err != nil {
			t.Errorf("cannot read all data for %v: %v", objectID, err)
			continue
		}

		if !bytes.Equal(d, c.payload) {
			t.Errorf("incorrect payload for %v: expected: %v got: %v", objectID, c.payload, d)
			continue
		}
	}
}

func TestReaderStoredBlockNotFound(t *testing.T) {
	ctx := testlogging.Context(t)
	_, _, om := setupTest(t, nil)

	objectID, err := ParseID("deadbeef")
	if err != nil {
		t.Errorf("cannot parse object ID: %v", err)
	}

	reader, err := Open(ctx, om.contentMgr, objectID)
	if !errors.Is(err, ErrObjectNotFound) || reader != nil {
		t.Errorf("unexpected result: reader: %v err: %v", reader, err)
	}
}

func TestEndToEndReadAndSeek(t *testing.T) {
	for _, asyncWrites := range []int{0, 4, 8} {
		asyncWrites := asyncWrites

		t.Run(fmt.Sprintf("async-%v", asyncWrites), func(t *testing.T) {
			t.Parallel()

			ctx := testlogging.Context(t)
			_, _, om := setupTest(t, nil)

			for _, size := range []int{1, 199, 200, 201, 9999, 512434, 5012434} {
				// Create some random data sample of the specified size.
				randomData := make([]byte, size)
				cryptorand.Read(randomData)

				writer := om.NewWriter(ctx, WriterOptions{AsyncWrites: asyncWrites})
				if _, err := writer.Write(randomData); err != nil {
					t.Errorf("write error: %v", err)
				}

				objectID, err := writer.Result()
				t.Logf("oid: %v", objectID)

				writer.Close()

				if err != nil {
					t.Errorf("cannot get writer result for %v: %v", size, err)
					continue
				}

				verify(ctx, t, om.contentMgr, objectID, randomData, fmt.Sprintf("%v %v", objectID, size))
			}
		})
	}
}

func TestEndToEndReadAndSeekWithCompression(t *testing.T) {
	sizes := []int{1, 199, 9999, 512434, 5012434, 15000000}

	if runtime.GOARCH != "amd64" {
		sizes = []int{1, 199, 9999, 512434}
	}

	compressibleData := map[int][]byte{}
	nonCompressibleData := map[int][]byte{}

	for _, s := range sizes {
		compressibleData[s] = makeMaybeCompressibleData(s, true)
		nonCompressibleData[s] = makeMaybeCompressibleData(s, false)
	}

	for _, compressible := range []bool{false, true} {
		compressible := compressible

		for compressorName := range compression.ByName {
			compressorName := compressorName
			t.Run(string(compressorName), func(t *testing.T) {
				ctx := testlogging.Context(t)

				totalBytesWritten := 0

				data, _, om := setupTest(t, nil)

				for _, size := range sizes {
					var inputData []byte

					if compressible {
						inputData = compressibleData[size]
					} else {
						inputData = nonCompressibleData[size]
					}

					writer := om.NewWriter(ctx, WriterOptions{Compressor: compressorName})
					if _, err := writer.Write(inputData); err != nil {
						t.Errorf("write error: %v", err)
					}

					totalBytesWritten += size

					objectID, err := writer.Result()

					writer.Close()

					if err != nil {
						t.Errorf("cannot get writer result for %v: %v", size, err)
						continue
					}

					verify(ctx, t, om.contentMgr, objectID, inputData, fmt.Sprintf("%v %v", objectID, size))
				}

				if compressible {
					compressedBytes := 0
					for _, d := range data {
						compressedBytes += len(d)
					}

					// data is highly compressible, should easily compress to 1% of original size or less
					ratio := float64(compressedBytes) / float64(totalBytesWritten)
					if ratio > 0.01 {
						t.Errorf("compression not effective for %v wrote %v, compressed %v, ratio %v", compressorName, totalBytesWritten, compressedBytes, ratio)
					}
				}
			})
		}
	}
}

func makeMaybeCompressibleData(size int, compressible bool) []byte {
	if compressible {
		phrase := []byte("quick brown fox")
		return append(append([]byte(nil), phrase[0:size%len(phrase)]...), bytes.Repeat(phrase, size/len(phrase))...)
	}

	b := make([]byte, size)
	cryptorand.Read(b)

	return b
}

func verify(ctx context.Context, t *testing.T, cr contentReader, objectID ID, expectedData []byte, testCaseID string) {
	t.Helper()

	reader, err := Open(ctx, cr, objectID)
	if err != nil {
		t.Errorf("cannot get reader for %v (%v): %v %v", testCaseID, objectID, err, string(debug.Stack()))
		return
	}

	for i := 0; i < 20; i++ {
		sampleSize := int(rand.Int31n(300))
		seekOffset := int(rand.Int31n(int32(len(expectedData))))

		if seekOffset+sampleSize > len(expectedData) {
			sampleSize = len(expectedData) - seekOffset
		}

		if sampleSize > 0 {
			got := make([]byte, sampleSize)

			if offset, err := reader.Seek(int64(seekOffset), 0); err != nil || offset != int64(seekOffset) {
				t.Errorf("seek error: %v offset=%v expected:%v", err, offset, seekOffset)
			}

			if n, err := reader.Read(got); err != nil || n != sampleSize {
				t.Errorf("invalid data: n=%v, expected=%v, err:%v", n, sampleSize, err)
			}

			expected := expectedData[seekOffset : seekOffset+sampleSize]

			if !bytes.Equal(expected, got) {
				t.Errorf("incorrect data read for %v: expected: %x, got: %x", testCaseID, expected, got)
			}
		}
	}
}

//nolint:gocyclo
func TestSeek(t *testing.T) {
	ctx := testlogging.Context(t)
	_, _, om := setupTest(t, nil)

	for _, size := range []int{0, 1, 500000, 15000000} {
		randomData := make([]byte, size)
		cryptorand.Read(randomData)

		writer := om.NewWriter(ctx, WriterOptions{})
		if _, err := writer.Write(randomData); err != nil {
			t.Errorf("write error: %v", err)
		}

		objectID, err := writer.Result()
		if err != nil {
			t.Fatalf("unable to write: %v", err)
		}

		r, err := Open(ctx, om.contentMgr, objectID)
		if err != nil {
			t.Fatalf("open error: %v", err)
		}

		if pos, err := r.Seek(0, io.SeekStart); err != nil || pos != 0 {
			t.Errorf("invalid seek-start result %v %v", pos, err)
		}

		if pos, err := r.Seek(0, io.SeekCurrent); err != nil || pos != 0 {
			t.Errorf("invalid seek-current at start result %v %v", pos, err)
		}

		if pos, err := r.Seek(0, io.SeekEnd); err != nil || pos != int64(size) {
			t.Errorf("invalid seek-end result %v %v", pos, err)
		}

		if pos, err := r.Seek(0, io.SeekCurrent); err != nil || pos != int64(size) {
			t.Errorf("invalid seek-current at end result %v %v, wanted %v", pos, err, size)
		}

		if pos, err := r.Seek(1, io.SeekCurrent); err != nil || pos != int64(size)+1 {
			t.Errorf("unexpected result when seeking past end of file: %v, %v, wanted %v", pos, err, size+1)
		}

		buf := make([]byte, 5)
		if n, err := r.Read(buf); n != 0 || !errors.Is(err, io.EOF) {
			t.Errorf("unexpected read result %v %v", n, err)
		}
	}
}

func TestWriterFlushFailure_OnWrite(t *testing.T) {
	_, fcm, om := setupTest(t, nil)

	ctx := testlogging.Context(t)
	w := om.NewWriter(ctx, WriterOptions{})

	fcm.writeContentError = errSomeError

	n, err := w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1e6))
	require.ErrorIs(t, err, errSomeError)
	require.Equal(t, n, 0)
}

func TestWriterFlushFailure_OnFlush(t *testing.T) {
	_, fcm, om := setupTest(t, nil)

	ctx := testlogging.Context(t)
	w := om.NewWriter(ctx, WriterOptions{})

	n, err := w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1e6))
	require.NoError(t, err, errSomeError)
	require.Equal(t, n, 4000000)

	fcm.writeContentError = errSomeError

	_, err = w.Result()
	require.ErrorIs(t, err, errSomeError)
}

func TestWriterFlushFailure_OnCheckpoint(t *testing.T) {
	_, fcm, om := setupTest(t, nil)

	ctx := testlogging.Context(t)
	w := om.NewWriter(ctx, WriterOptions{})

	w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1e6))

	fcm.writeContentError = errSomeError
	_, err := w.Checkpoint()

	require.ErrorIs(t, err, errSomeError)
}

func TestWriterFlushFailure_OnAsyncWrite(t *testing.T) {
	_, fcm, om := setupTest(t, nil)

	ctx := testlogging.Context(t)
	w := om.NewWriter(ctx, WriterOptions{
		AsyncWrites: 1,
	})

	fcm.writeContentError = errSomeError

	n, err := w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1e6))
	require.NoError(t, err, errSomeError)
	require.Equal(t, n, 4000000)

	_, err = w.Result()
	require.ErrorIs(t, err, errSomeError)
}

type faultyCompressor struct{}

func (faultyCompressor) Compress(output io.Writer, input io.Reader) error {
	return errSomeError
}

func (faultyCompressor) Decompress(output io.Writer, input io.Reader, withHeader bool) error {
	return nil
}

func (faultyCompressor) HeaderID() compression.HeaderID {
	return 0
}

func TestWriterFailure_OnCompression(t *testing.T) {
	_, _, om := setupTest(t, nil)

	compression.RegisterCompressor("faulty", &faultyCompressor{})

	ctx := testlogging.Context(t)
	w := om.NewWriter(ctx, WriterOptions{
		Compressor: "faulty",
	})

	_, err := w.Write(bytes.Repeat([]byte{1, 2, 3, 4}, 1e6))
	require.Error(t, err, errSomeError)
}


text:
    <form class="signin form-horizontal">
      <fieldset>

              Wybierz zdjęcie <input type="file" nv-file-select uploader="uploader">

          <button class="btn btn-primary" ng-click="uploadProfilePicture();">Dodaj</button>
          <button class="btn btn-default" ng-click="cancelUpload();">Anuluj</button>

          Zmiana zdjęcia powiodła się

      </fieldset>
    </form>

redpajama_set_name: RedPajamaGithub


text:
from distutils.core import setup

setup(
    name='django-compound-forms',
    version='0.1.0',
    author='Julien Hartmann',
    author_email='juli1.hartmann@gmail.com',
    packages=['compound_forms'],
    url='http://pypi.python.org/pypi/django-compound-forms/',
    license='LICENSE',
    description='Dynamic and static form composition for Django.',
    long_description=open('README.rst').read(),
    install_requires=[
        "Django >= 1.6",
    ],
)


text:
'use strict';

import 'vs/css!./media/editorGroupsControl';
import arrays = require('vs/base/common/arrays');
import Event, { Emitter } from 'vs/base/common/event';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import types = require('vs/base/common/types');
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import DOM = require('vs/base/browser/dom');
import errors = require('vs/base/common/errors');
import { RunOnceScheduler } from 'vs/base/common/async';
import { isMacintosh } from 'vs/base/common/platform';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Position, POSITIONS } from 'vs/platform/editor/common/editor';
import { IEditorGroupService, IEditorTabOptions, GroupArrangement, GroupOrientation } from 'vs/workbench/services/group/common/groupService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { TitleControl, ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl';
import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier } from 'vs/workbench/common/editor';
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme';
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { EditorAreaDropHandler } from 'vs/workbench/browser/dnd';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

export enum Rochade {
	NONE,
	TWO_TO_ONE,
	THREE_TO_TWO,
	TWO_AND_THREE_TO_ONE
}

export enum ProgressState {
	INFINITE,
	DONE,
	STOP
}

export interface IEditorGroupsControl {

	onGroupFocusChanged: Event<void>;

	show(editor: BaseEditor, position: Position, preserveActive: boolean, ratio?: number[]): void;
	hide(editor: BaseEditor, position: Position, layoutAndRochade: boolean): Rochade;

	setActive(editor: BaseEditor): void;

	getActiveEditor(): BaseEditor;
	getActivePosition(): Position;

	move(from: Position, to: Position): void;

	isDragging(): boolean;

	getInstantiationService(position: Position): IInstantiationService;
	getProgressBar(position: Position): ProgressBar;
	updateProgress(position: Position, state: ProgressState): void;
	updateTitleAreas(refreshActive?: boolean): void;

	layout(dimension: Dimension): void;
	layout(position: Position): void;

	arrangeGroups(arrangement: GroupArrangement): void;

	setGroupOrientation(orientation: GroupOrientation): void;
	getGroupOrientation(): GroupOrientation;

	resizeGroup(position: Position, groupSizeChange: number): void;

	getRatio(): number[];

	dispose(): void;
}

/**
 * Helper class to manage multiple side by side editors for the editor part.
 */
export class EditorGroupsControl extends Themable implements IEditorGroupsControl, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {

	private static readonly TITLE_AREA_CONTROL_KEY = '__titleAreaControl';
	private static readonly PROGRESS_BAR_CONTROL_KEY = '__progressBar';
	private static readonly INSTANTIATION_SERVICE_KEY = '__instantiationService';

	private static readonly MIN_EDITOR_WIDTH = 170;
	private static readonly MIN_EDITOR_HEIGHT = 70;

	private static readonly EDITOR_TITLE_HEIGHT = 35;

	private static readonly SNAP_TO_MINIMIZED_THRESHOLD_WIDTH = 50;
	private static readonly SNAP_TO_MINIMIZED_THRESHOLD_HEIGHT = 20;

	private stacks: IEditorStacksModel;

	private parent: Builder;
	private dimension: Dimension;
	private dragging: boolean;

	private layoutVertically: boolean;

	private tabOptions: IEditorTabOptions;

	private silos: Builder[];
	private silosSize: number[];
	private silosInitialRatio: number[];
	private silosMinimized: boolean[];

	private sashOne: Sash;
	private startSiloOneSize: number;

	private sashTwo: Sash;
	private startSiloThreeSize: number;

	private visibleEditors: BaseEditor[];

	private lastActiveEditor: BaseEditor;
	private lastActivePosition: Position;

	private visibleEditorFocusTrackerDisposable: IDisposable[];

	private _onGroupFocusChanged: Emitter<void>;

	private onStacksChangeScheduler: RunOnceScheduler;
	private stacksChangedBuffer: IStacksModelChangeEvent[];

	constructor(
		parent: Builder,
		groupOrientation: GroupOrientation,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IEditorGroupService private editorGroupService: IEditorGroupService,
		@IContextKeyService private contextKeyService: IContextKeyService,
		@IExtensionService private extensionService: IExtensionService,
		@IInstantiationService private instantiationService: IInstantiationService,
		@IThemeService themeService: IThemeService,
		@ITelemetryService private telemetryService: ITelemetryService
	) {
		super(themeService);

		this.stacks = editorGroupService.getStacksModel();

		this.parent = parent;
		this.dimension = new Dimension(0, 0);

		this.silos = [];
		this.silosSize = [];
		this.silosMinimized = [];

		this.visibleEditors = [];
		this.visibleEditorFocusTrackerDisposable = [];

		this._onGroupFocusChanged = new Emitter<void>();
		this.toUnbind.push(this._onGroupFocusChanged);

		this.onStacksChangeScheduler = new RunOnceScheduler(() => this.handleStacksChanged(), 0);
		this.toUnbind.push(this.onStacksChangeScheduler);
		this.stacksChangedBuffer = [];

		this.updateTabOptions(this.editorGroupService.getTabOptions());

		const editorGroupOrientation = groupOrientation || 'vertical';
		this.layoutVertically = (editorGroupOrientation !== 'horizontal');

		this.create();

		this.registerListeners();
	}

	private get totalSize(): number {
		if (!this.dimension || !this.dimension.width || !this.dimension.height) {
			return 0;
		}

		return this.layoutVertically ? this.dimension.width : this.dimension.height;
	}

	private get minSize(): number {
		return this.layoutVertically ? EditorGroupsControl.MIN_EDITOR_WIDTH : EditorGroupsControl.MIN_EDITOR_HEIGHT;
	}

	private isSiloMinimized(position: number): boolean {
		return this.silosSize[position] === this.minSize && this.silosMinimized[position];
	}

	private enableMinimizedState(): void {
		POSITIONS.forEach(p => this.silosMinimized[p] = this.silosSize[p] === this.minSize);
	}

	private updateMinimizedState(): void {
		POSITIONS.forEach(p => {
			if (this.silosSize[p] !== this.minSize) {
				this.silosMinimized[p] = false; // release silo from minimized state if it was sized large enough
			}
		});
	}

	private get snapToMinimizeThresholdSize(): number {
		return this.layoutVertically ? EditorGroupsControl.SNAP_TO_MINIMIZED_THRESHOLD_WIDTH : EditorGroupsControl.SNAP_TO_MINIMIZED_THRESHOLD_HEIGHT;
	}

	private registerListeners(): void {
		this.toUnbind.push(this.stacks.onModelChanged(e => this.onStacksChanged(e)));
		this.toUnbind.push(this.editorGroupService.onTabOptionsChanged(options => this.updateTabOptions(options, true)));
		this.toUnbind.push(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions()));
	}

	private updateTabOptions(tabOptions: IEditorTabOptions, refresh?: boolean): void {
		const tabCloseButton = this.tabOptions ? this.tabOptions.tabCloseButton : 'right';
		const tabSizing = this.tabOptions ? this.tabOptions.tabSizing : 'fit';
		const iconTheme = this.tabOptions ? this.tabOptions.iconTheme : 'vs-seti';

		this.tabOptions = tabOptions;

		if (!refresh) {
			return; // return early if no refresh is needed
		}

		// Editor Containers
		POSITIONS.forEach(position => {
			const titleControl = this.getTitleAreaControl(position);

			// Title Container
			const titleContainer = $(titleControl.getContainer());
			if (this.tabOptions.showTabs) {
				titleContainer.addClass('tabs');
			} else {
				titleContainer.removeClass('tabs');
			}

			const showingIcons = titleContainer.hasClass('show-file-icons');
			if (this.tabOptions.showIcons) {
				titleContainer.addClass('show-file-icons');
			} else {
				titleContainer.removeClass('show-file-icons');
			}

			// Title Control
			if (titleControl) {
				const usingTabs = (titleControl instanceof TabsTitleControl);

				// Recreate title when tabs change
				if (usingTabs !== this.tabOptions.showTabs) {
					titleControl.dispose();
					titleContainer.empty();
					this.createTitleControl(this.stacks.groupAt(position), this.silos[position], titleContainer, this.getInstantiationService(position));
				}

				// Refresh title when layout options change
				else if (
					showingIcons !== this.tabOptions.showIcons ||
					tabCloseButton !== this.tabOptions.tabCloseButton ||
					tabSizing !== this.tabOptions.tabSizing ||
					iconTheme !== this.tabOptions.iconTheme
				) {
					titleControl.refresh();
				}
			}

			// Update Styles
			this.updateStyles();
		});
	}

	private onDidRegisterExtensions(): void {

		// Up to date title areas
		POSITIONS.forEach(position => this.getTitleAreaControl(position).update());
	}

	private onStacksChanged(e: IStacksModelChangeEvent): void {
		this.stacksChangedBuffer.push(e);
		this.onStacksChangeScheduler.schedule();
	}

	private handleStacksChanged(): void {

		// Read and reset buffer of events
		const buffer = this.stacksChangedBuffer;
		this.stacksChangedBuffer = [];

		// Up to date context for all title controls
		POSITIONS.forEach(position => {
			const titleAreaControl = this.getTitleAreaControl(position);
			const context = this.stacks.groupAt(position);
			const hasContext = titleAreaControl.hasContext();
			titleAreaControl.setContext(context);
			if (!context && hasContext) {
				titleAreaControl.refresh(); // clear out the control if the context is no longer present and there was a context
			}
		});

		// Refresh / update if group is visible and has a position
		buffer.forEach(e => {
			const position = this.stacks.positionOfGroup(e.group);
			if (position >= 0) { // group could be gone by now because we run from a scheduler with timeout
				if (e.structural) {
					this.getTitleAreaControl(position).refresh();
				} else {
					this.getTitleAreaControl(position).update();
				}
			}
		});
	}

	public get onGroupFocusChanged(): Event<void> {
		return this._onGroupFocusChanged.event;
	}

	public show(editor: BaseEditor, position: Position, preserveActive: boolean, ratio?: number[]): void {
		const visibleEditorCount = this.getVisibleEditorCount();

		// Store into editor bucket
		this.visibleEditors[position] = editor;

		// Store as active unless preserveActive is set
		if (!preserveActive || !this.lastActiveEditor) {
			this.doSetActive(editor, position);
		}

		// Track focus
		this.trackFocus(editor, position);

		// Find target container and build into
		const target = this.silos[position].child();
		editor.getContainer().build(target);

		// Adjust layout according to provided ratios (used when restoring multiple editors at once)
		if (ratio && (ratio.length === 2 || ratio.length === 3)) {
			const hasLayoutInfo = !!this.totalSize;

			// We received ratios but were not layouted yet. So we keep these ratios for when we layout()
			if (!hasLayoutInfo) {
				this.silosInitialRatio = ratio;
			}

			// Adjust layout: -> [!][!]
			if (ratio.length === 2) {
				if (hasLayoutInfo) {
					this.silosSize[position] = this.totalSize * ratio[position];
				}
			}

			// Adjust layout: -> [!][!][!]
			else if (ratio.length === 3) {
				if (hasLayoutInfo) {
					this.silosSize[position] = this.totalSize * ratio[position];
				}

				if (this.sashTwo.isHidden()) {
					this.sashTwo.show();
					this.sashTwo.layout();
				}
			}

			if (this.sashOne.isHidden()) {
				this.sashOne.show();
				this.sashOne.layout();
			}

			if (hasLayoutInfo) {
				this.layoutContainers();
			}
		}

		// Adjust layout: -> [!]
		else if (visibleEditorCount === 0 && this.dimension) {
			this.silosSize[position] = this.totalSize;

			this.layoutContainers();
		}

		// Adjust layout: [] -> []|[!]
		else if (position === Position.TWO && this.sashOne.isHidden() && this.sashTwo.isHidden() && this.dimension) {
			this.silosSize[Position.ONE] = this.totalSize / 2;
			this.silosSize[Position.TWO] = this.totalSize - this.silosSize[Position.ONE];

			this.sashOne.show();
			this.sashOne.layout();

			this.layoutContainers();
		}

		// Adjust layout: []|[] -> []|[]|[!]
		else if (position === Position.THREE && this.sashTwo.isHidden() && this.dimension) {
			this.silosSize[Position.ONE] = this.totalSize / 3;
			this.silosSize[Position.TWO] = this.totalSize / 3;
			this.silosSize[Position.THREE] = this.totalSize - this.silosSize[Position.ONE] - this.silosSize[Position.TWO];

			this.sashOne.layout();
			this.sashTwo.show();
			this.sashTwo.layout();

			this.layoutContainers();
		}

		// Show editor container
		editor.getContainer().show();
	}

	private getVisibleEditorCount(): number {
		return this.visibleEditors.filter(v => !!v).length;
	}

	private trackFocus(editor: BaseEditor, position: Position): void {

		// In case there is a previous tracker on the position, dispose it first
		if (this.visibleEditorFocusTrackerDisposable[position]) {
			this.visibleEditorFocusTrackerDisposable[position].dispose();
		}

		// Track focus on editor container
		const focusTracker = DOM.trackFocus(editor.getContainer().getHTMLElement());
		const listenerDispose = focusTracker.onDidFocus(() => {
			this.onFocusGained(editor);
		});

		this.visibleEditorFocusTrackerDisposable[position] = combinedDisposable([focusTracker, listenerDispose]);
	}

	private onFocusGained(editor: BaseEditor): void {
		this.setActive(editor);
	}

	public setActive(editor: BaseEditor): void {

		// Update active editor and position
		if (this.lastActiveEditor !== editor) {
			this.doSetActive(editor, this.visibleEditors.indexOf(editor));

			// Automatically maximize this position if it is minimized
			if (this.isSiloMinimized(this.lastActivePosition)) {
				let remainingSize = this.totalSize;
				let layout = false;

				// Minimize all other positions to min size
				POSITIONS.forEach(p => {
					if (this.lastActivePosition !== p && !!this.visibleEditors[p]) {
						this.silosSize[p] = this.minSize;
						remainingSize -= this.silosSize[p];
					}
				});

				// Grow focused position if there is more size to spend
				if (remainingSize > this.minSize) {
					this.silosSize[this.lastActivePosition] = remainingSize;

					if (!this.sashOne.isHidden()) {
						this.sashOne.layout();
					}

					if (!this.sashTwo.isHidden()) {
						this.sashTwo.layout();
					}

					layout = true;
				}

				// Since we triggered a change in minimized/maximized editors, we need
				// to update our stored state of minimized silos accordingly
				this.enableMinimizedState();

				if (layout) {
					this.layoutContainers();
				}
			}

			// Re-emit to outside
			this._onGroupFocusChanged.fire();
		}
	}

	private focusNextNonMinimized(): void {

		// If the current focused editor is minimized, try to focus the next largest editor
		if (!types.isUndefinedOrNull(this.lastActivePosition) && this.silosMinimized[this.lastActivePosition]) {
			let candidate: Position = null;
			let currentSize = this.minSize;
			POSITIONS.forEach(position => {

				// Skip current active position and check if the editor is larger than min size
				if (position !== this.lastActivePosition) {
					if (this.visibleEditors[position] && this.silosSize[position] > currentSize) {
						candidate = position;
						currentSize = this.silosSize[position];
					}
				}
			});

			// Focus editor if a candidate has been found
			if (!types.isUndefinedOrNull(candidate)) {
				this.editorGroupService.focusGroup(candidate);
			}
		}
	}

	public hide(editor: BaseEditor, position: Position, layoutAndRochade: boolean): Rochade {
		let result = Rochade.NONE;

		const visibleEditorCount = this.getVisibleEditorCount();

		const hasEditorInPositionTwo = !!this.visibleEditors[Position.TWO];
		const hasEditorInPositionThree = !!this.visibleEditors[Position.THREE];

		// If editor is not showing for position, return
		if (editor !== this.visibleEditors[position]) {
			return result;
		}

		// Clear Position
		this.clearPosition(position);

		// Take editor container offdom and hide
		editor.getContainer().offDOM().hide();

		// Adjust layout and rochade if instructed to do so
		if (layoutAndRochade) {

			// Adjust layout: [x] ->
			if (visibleEditorCount === 1) {
				this.silosSize[position] = 0;

				this.sashOne.hide();
				this.sashTwo.hide();

				this.layoutContainers();
			}

			// Adjust layout: []|[x] -> [] or [x]|[] -> []
			else if (hasEditorInPositionTwo && !hasEditorInPositionThree) {
				this.silosSize[Position.ONE] = this.totalSize;
				this.silosSize[Position.TWO] = 0;

				this.sashOne.hide();
				this.sashTwo.hide();

				// Move TWO to ONE ([x]|[] -> [])
				if (position === Position.ONE) {
					this.rochade(Position.TWO, Position.ONE);
					result = Rochade.TWO_TO_ONE;
				}

				this.layoutContainers();
			}

			// Adjust layout: []|[]|[x] -> [ ]|[ ] or []|[x]|[] -> [ ]|[ ] or [x]|[]|[] -> [ ]|[ ]
			else if (hasEditorInPositionTwo && hasEditorInPositionThree) {
				this.silosSize[Position.ONE] = this.totalSize / 2;
				this.silosSize[Position.TWO] = this.totalSize - this.silosSize[Position.ONE];
				this.silosSize[Position.THREE] = 0;

				this.sashOne.layout();
				this.sashTwo.hide();

				// Move THREE to TWO ([]|[x]|[] -> [ ]|[ ])
				if (position === Position.TWO) {
					this.rochade(Position.THREE, Position.TWO);
					result = Rochade.THREE_TO_TWO;
				}

				// Move THREE to TWO and TWO to ONE ([x]|[]|[] -> [ ]|[ ])
				else if (position === Position.ONE) {
					this.rochade(Position.TWO, Position.ONE);
					this.rochade(Position.THREE, Position.TWO);
					result = Rochade.TWO_AND_THREE_TO_ONE;
				}

				this.layoutContainers();
			}
		}

		// Automatically pick the next editor as active if any
		if (this.lastActiveEditor === editor) {

			// Clear old
			this.doSetActive(null, null);

			// Find new active position by taking the next one close to the closed one to the left/top
			if (layoutAndRochade) {
				let newActivePosition: Position;
				switch (position) {
					case Position.ONE:
						newActivePosition = hasEditorInPositionTwo ? Position.ONE : null;
						break;
					case Position.TWO:
						newActivePosition = Position.ONE;
						break;
					case Position.THREE:
						newActivePosition = Position.TWO;
						break;
				}

				if (!types.isUndefinedOrNull(newActivePosition)) {
					this.doSetActive(this.visibleEditors[newActivePosition], newActivePosition);
				}
			}
		}

		return result;
	}

	private doSetActive(editor: BaseEditor, newActive: Position): void {
		this.lastActivePosition = newActive;
		this.lastActiveEditor = editor;
	}

	private clearPosition(position: Position): void {

		// Unregister Listeners
		if (this.visibleEditorFocusTrackerDisposable[position]) {
			this.visibleEditorFocusTrackerDisposable[position].dispose();
			this.visibleEditorFocusTrackerDisposable[position] = null;
		}

		// Clear from active editors
		this.visibleEditors[position] = null;
	}

	private rochade(from: Position, to: Position): void {

		// Move container to new position
		const containerFrom = this.silos[from].child();
		containerFrom.appendTo(this.silos[to]);

		const containerTo = this.silos[to].child();
		containerTo.appendTo(this.silos[from]);

		// Inform editor
		const editor = this.visibleEditors[from];
		editor.changePosition(to);

		// Change data structures
		const listeners = this.visibleEditorFocusTrackerDisposable[from];
		this.visibleEditorFocusTrackerDisposable[to] = listeners;
		this.visibleEditorFocusTrackerDisposable[from] = null;

		const minimizedState = this.silosMinimized[from];
		this.silosMinimized[to] = minimizedState;
		this.silosMinimized[from] = null;

		this.visibleEditors[to] = editor;
		this.visibleEditors[from] = null;

		// Update last active position
		if (this.lastActivePosition === from) {
			this.doSetActive(this.lastActiveEditor, to);
		}
	}

	public move(from: Position, to: Position): void {

		// Distance 1: Swap Editors
		if (Math.abs(from - to) === 1) {

			// Move containers to new position
			const containerFrom = this.silos[from].child();
			containerFrom.appendTo(this.silos[to]);

			const containerTo = this.silos[to].child();
			containerTo.appendTo(this.silos[from]);

			// Inform Editors
			this.visibleEditors[from].changePosition(to);
			this.visibleEditors[to].changePosition(from);

			// Update last active position accordingly
			if (this.lastActivePosition === from) {
				this.doSetActive(this.lastActiveEditor, to);
			} else if (this.lastActivePosition === to) {
				this.doSetActive(this.lastActiveEditor, from);
			}
		}

		// Otherwise Move Editors
		else {

			// Find new positions
			let newPositionOne: Position;
			let newPositionTwo: Position;
			let newPositionThree: Position;

			if (from === Position.ONE) {
				newPositionOne = Position.THREE;
				newPositionTwo = Position.ONE;
				newPositionThree = Position.TWO;
			} else {
				newPositionOne = Position.TWO;
				newPositionTwo = Position.THREE;
				newPositionThree = Position.ONE;
			}

			// Move containers to new position
			const containerPos1 = this.silos[Position.ONE].child();
			containerPos1.appendTo(this.silos[newPositionOne]);

			const containerPos2 = this.silos[Position.TWO].child();
			containerPos2.appendTo(this.silos[newPositionTwo]);

			const containerPos3 = this.silos[Position.THREE].child();
			containerPos3.appendTo(this.silos[newPositionThree]);

			// Inform Editors
			this.visibleEditors[Position.ONE].changePosition(newPositionOne);
			this.visibleEditors[Position.TWO].changePosition(newPositionTwo);
			this.visibleEditors[Position.THREE].changePosition(newPositionThree);

			// Update last active position accordingly
			if (this.lastActivePosition === Position.ONE) {
				this.doSetActive(this.lastActiveEditor, newPositionOne);
			} else if (this.lastActivePosition === Position.TWO) {
				this.doSetActive(this.lastActiveEditor, newPositionTwo);
			} else if (this.lastActivePosition === Position.THREE) {
				this.doSetActive(this.lastActiveEditor, newPositionThree);
			}
		}

		// Change data structures
		arrays.move(this.visibleEditors, from, to);
		arrays.move(this.visibleEditorFocusTrackerDisposable, from, to);
		arrays.move(this.silosSize, from, to);
		arrays.move(this.silosMinimized, from, to);

		// Layout
		if (!this.sashOne.isHidden()) {
			this.sashOne.layout();
		}

		if (!this.sashTwo.isHidden()) {
			this.sashTwo.layout();
		}

		this.layoutContainers();
		this.updateStyles();
	}

	public setGroupOrientation(orientation: GroupOrientation): void {
		this.layoutVertically = (orientation !== 'horizontal');

		// Editor Layout
		const verticalLayouting = this.parent.hasClass('vertical-layout');
		if (verticalLayouting !== this.layoutVertically) {
			this.parent.removeClass('vertical-layout', 'horizontal-layout');
			this.parent.addClass(this.layoutVertically ? 'vertical-layout' : 'horizontal-layout');

			this.sashOne.setOrientation(this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL);
			this.sashTwo.setOrientation(this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL);

			// Update styles
			this.updateStyles();

			// Trigger layout
			this.arrangeGroups();
		}
	}

	public getGroupOrientation(): GroupOrientation {
		return this.layoutVertically ? 'vertical' : 'horizontal';
	}

	public arrangeGroups(arrangement?: GroupArrangement): void {
		if (!this.dimension) {
			return; // too early
		}

		let availableSize = this.totalSize;
		const visibleEditors = this.getVisibleEditorCount();

		if (visibleEditors <= 1) {
			return; // need more editors
		}

		switch (arrangement) {
			case GroupArrangement.MINIMIZE_OTHERS:
				// Minimize Others
				POSITIONS.forEach(position => {
					if (this.visibleEditors[position]) {
						if (position !== this.lastActivePosition) {
							this.silosSize[position] = this.minSize;
							availableSize -= this.minSize;
						}
					}
				});

				this.silosSize[this.lastActivePosition] = availableSize;
				break;
			case GroupArrangement.EVEN:
				// Even Sizes
				POSITIONS.forEach(position => {
					if (this.visibleEditors[position]) {
						this.silosSize[position] = availableSize / visibleEditors;
					}
				});
				break;
			default:
				// Minimized editors should remain minimized, others should keep their relative Sizes
				let oldNonMinimizedTotal = 0;
				POSITIONS.forEach(position => {
					if (this.visibleEditors[position]) {
						if (this.silosMinimized[position]) {
							this.silosSize[position] = this.minSize;
							availableSize -= this.minSize;
						} else {
							oldNonMinimizedTotal += this.silosSize[position];
						}
					}
				});

				// Set size for non-minimized editors
				const scaleFactor = availableSize / oldNonMinimizedTotal;
				POSITIONS.forEach(position => {
					if (this.visibleEditors[position] && !this.silosMinimized[position]) {
						this.silosSize[position] *= scaleFactor;
					}
				});
		}

		// Since we triggered a change in minimized/maximized editors, we need
		// to update our stored state of minimized silos accordingly
		this.enableMinimizedState();

		// Layout silos
		this.layoutControl(this.dimension);
	}

	public getRatio(): number[] {
		const ratio: number[] = [];

		if (this.dimension) {
			const fullSize = this.totalSize;

			POSITIONS.forEach(position => {
				if (this.visibleEditors[position]) {
					ratio.push(this.silosSize[position] / fullSize);
				}
			});
		}

		return ratio;
	}

	// Resize the editor/group position - changes main axis
	public resizeGroup(position: Position, groupSizeChange: number): void {

		enum VISIBLE_EDITORS {
			ONE = 1,
			TWO = 2,
			THREE = 3
		}

		const visibleEditors = this.getVisibleEditorCount();

		if (visibleEditors <= VISIBLE_EDITORS.ONE) {
			return;
		}

		const availableSize = this.totalSize;
		const activeGroupPosition = this.getActivePosition();

		switch (visibleEditors) {
			case VISIBLE_EDITORS.TWO:
				switch (activeGroupPosition) {
					case Position.ONE:
						this.silosSize[Position.ONE] = this.boundSiloSize(Position.ONE, groupSizeChange);
						this.silosSize[Position.TWO] = availableSize - this.silosSize[Position.ONE];
						break;
					case Position.TWO:
						this.silosSize[Position.TWO] = this.boundSiloSize(Position.TWO, groupSizeChange);
						this.silosSize[Position.ONE] = availableSize - this.silosSize[Position.TWO];
					default:
						break;
				}
				break;
			case VISIBLE_EDITORS.THREE:
				switch (activeGroupPosition) {
					case Position.ONE:
						this.silosSize[Position.ONE] = this.boundSiloSize(Position.ONE, groupSizeChange);
						this.distributeRemainingSilosSize(Position.TWO, Position.THREE, availableSize - this.silosSize[Position.ONE]);
						break;
					case Position.TWO:
						this.silosSize[Position.TWO] = this.boundSiloSize(Position.TWO, groupSizeChange);
						this.distributeRemainingSilosSize(Position.ONE, Position.THREE, availableSize - this.silosSize[Position.TWO]);
						break;
					case Position.THREE:
						this.silosSize[Position.THREE] = this.boundSiloSize(Position.THREE, groupSizeChange);
						this.distributeRemainingSilosSize(Position.ONE, Position.TWO, availableSize - this.silosSize[Position.THREE]);
						break;
					default:
						break;
				}
			default:
				break;
		}

		this.layout(this.dimension);
	}

	private boundSiloSize(siloPosition: Position, sizeChangePx: number): number {
		const visibleEditors = this.getVisibleEditorCount();
		let newSiloSize: number = 0;

		newSiloSize = Math.max(this.minSize, this.silosSize[siloPosition] + sizeChangePx);
		newSiloSize = Math.min(newSiloSize, (this.totalSize - this.minSize * (visibleEditors - 1)));

		return newSiloSize;
	}

	private distributeRemainingSilosSize(remPosition1: Position, remPosition2: Position, availableSize: number): void {
		let scaleFactor: number = 0;

		scaleFactor = this.silosSize[remPosition1] / (this.silosSize[remPosition1] + this.silosSize[remPosition2]);
		this.silosSize[remPosition1] = scaleFactor * availableSize;
		this.silosSize[remPosition1] = Math.max(this.silosSize[remPosition1], this.minSize);
		this.silosSize[remPosition1] = Math.min(this.silosSize[remPosition1], (availableSize - this.minSize));
		this.silosSize[remPosition2] = availableSize - this.silosSize[remPosition1];
	}

	public getActiveEditor(): BaseEditor {
		return this.lastActiveEditor;
	}

	public getActivePosition(): Position {
		return this.lastActivePosition;
	}

	private create(): void {

		// Store layout as class property
		this.parent.addClass(this.layoutVertically ? 'vertical-layout' : 'horizontal-layout');

		// Allow to drop into container to open
		this.enableDropTarget(this.parent.getHTMLElement());

		// Silo One
		this.silos[Position.ONE] = $(this.parent).div({ class: 'one-editor-silo editor-one' });

		// Sash One
		this.sashOne = new Sash(this.parent.getHTMLElement(), this, { baseSize: 5, orientation: this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL });
		this.toUnbind.push(this.sashOne.onDidStart(() => this.onSashOneDragStart()));
		this.toUnbind.push(this.sashOne.onDidChange((e: ISashEvent) => this.onSashOneDrag(e)));
		this.toUnbind.push(this.sashOne.onDidEnd(() => this.onSashOneDragEnd()));
		this.toUnbind.push(this.sashOne.onDidReset(() => this.onSashOneReset()));
		this.sashOne.hide();

		// Silo Two
		this.silos[Position.TWO] = $(this.parent).div({ class: 'one-editor-silo editor-two' });

		// Sash Two
		this.sashTwo = new Sash(this.parent.getHTMLElement(), this, { baseSize: 5, orientation: this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL });
		this.toUnbind.push(this.sashTwo.onDidStart(() => this.onSashTwoDragStart()));
		this.toUnbind.push(this.sashTwo.onDidChange((e: ISashEvent) => this.onSashTwoDrag(e)));
		this.toUnbind.push(this.sashTwo.onDidEnd(() => this.onSashTwoDragEnd()));
		this.toUnbind.push(this.sashTwo.onDidReset(() => this.onSashTwoReset()));
		this.sashTwo.hide();

		// Silo Three
		this.silos[Position.THREE] = $(this.parent).div({ class: 'one-editor-silo editor-three' });

		// For each position
		POSITIONS.forEach(position => {
			const silo = this.silos[position];

			// Containers (they contain everything and can move between silos)
			const container = $(silo).div({ 'class': 'container' });

			// InstantiationServices
			const instantiationService = this.instantiationService.createChild(new ServiceCollection(
				[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
			));
			container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container

			// Title containers
			const titleContainer = $(container).div({ 'class': 'title' });
			if (this.tabOptions.showTabs) {
				titleContainer.addClass('tabs');
			}
			if (this.tabOptions.showIcons) {
				titleContainer.addClass('show-file-icons');
			}
			this.hookTitleDragListener(titleContainer);

			// Title Control
			this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);

			// Progress Bar
			const progressBar = new ProgressBar($(container));
			this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
			progressBar.getContainer().hide();
			container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container
		});

		// Update Styles
		this.updateStyles();
	}

	protected updateStyles(): void {
		super.updateStyles();

		// Editor container colors
		this.silos.forEach((silo, index) => {

			// Background
			silo.style('background-color', this.getColor(editorBackground));

			// Border
			silo.style('border-left-color', index > Position.ONE ? (this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder)) : null);
			silo.style('border-top-color', index > Position.ONE ? (this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder)) : null);
		});

		// Title control
		POSITIONS.forEach(position => {
			const container = this.getTitleAreaControl(position).getContainer();
			const borderColor = this.getColor(EDITOR_GROUP_HEADER_TABS_BORDER) || this.getColor(contrastBorder);

			container.style.backgroundColor = this.getColor(this.tabOptions.showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND);
			container.style.borderBottomWidth = (borderColor && this.tabOptions.showTabs) ? '1px' : null;
			container.style.borderBottomStyle = (borderColor && this.tabOptions.showTabs) ? 'solid' : null;
			container.style.borderBottomColor = this.tabOptions.showTabs ? borderColor : null;
		});
	}

	private enableDropTarget(node: HTMLElement): void {
		const $this = this;
		const overlayId = 'monaco-workbench-editor-drop-overlay';
		const splitToPropertyKey = 'splitToPosition';
		const stacks = this.editorGroupService.getStacksModel();

		let overlay: Builder;

		function cleanUp(): void {
			if (overlay) {
				overlay.destroy();
				overlay = void 0;
			}

			POSITIONS.forEach(p => {
				$this.silos[p].removeClass('dragged-over');
			});
		}

		function optionsFromDraggedEditor(identifier: IEditorIdentifier): EditorOptions {

			// When moving an editor, try to preserve as much view state as possible by checking
			// for th editor to be a text editor and creating the options accordingly if so
			let options = EditorOptions.create({ pinned: true });
			const activeEditor = $this.editorService.getActiveEditor();
			const editor = getCodeEditor(activeEditor);
			if (editor && activeEditor.position === stacks.positionOfGroup(identifier.group) && identifier.editor.matches(activeEditor.input)) {
				options = TextEditorOptions.fromEditor(editor, { pinned: true });
			}

			return options;
		}

		function onDrop(e: DragEvent, position: Position, splitTo?: Position): void {
			$this.updateFromDragAndDrop(node, false);
			cleanUp();

			const editorService = $this.editorService;
			const groupService = $this.editorGroupService;

			const splitEditor = (typeof splitTo === 'number');
			const freeGroup = (stacks.groups.length === 1) ? Position.TWO : Position.THREE;

			// Check for transfer from title control
			const draggedEditor = TitleControl.getDraggedEditor();
			if (draggedEditor) {
				const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);

				// Copy editor to new location
				if (isCopy) {
					if (splitEditor) {
						editorService.openEditor(draggedEditor.editor, optionsFromDraggedEditor(draggedEditor), freeGroup).then(() => {
							if (splitTo !== freeGroup) {
								groupService.moveGroup(freeGroup, splitTo);
							}
						}).done(null, errors.onUnexpectedError);
					} else {
						editorService.openEditor(draggedEditor.editor, optionsFromDraggedEditor(draggedEditor), position).done(null, errors.onUnexpectedError);
					}
				}

				// Move editor to new location
				else {
					const sourcePosition = stacks.positionOfGroup(draggedEditor.group);
					if (splitEditor) {
						if (draggedEditor.group.count === 1) {
							groupService.moveGroup(sourcePosition, splitTo);
						} else {
							editorService.openEditor(draggedEditor.editor, optionsFromDraggedEditor(draggedEditor), freeGroup).then(() => {
								if (splitTo !== freeGroup) {
									groupService.moveGroup(freeGroup, splitTo);
								}
								groupService.moveEditor(draggedEditor.editor, stacks.positionOfGroup(draggedEditor.group), splitTo);
							}).done(null, errors.onUnexpectedError);
						}

					} else {
						groupService.moveEditor(draggedEditor.editor, sourcePosition, position);
					}
				}
			}

			// Check for URI transfer
			else {
				const dropHandler = $this.instantiationService.createInstance(EditorAreaDropHandler);
				dropHandler.handleDrop(e, () => {
					if (splitEditor && splitTo !== freeGroup) {
						groupService.moveGroup(freeGroup, splitTo);
					}

					groupService.focusGroup(splitEditor ? splitTo : position);
				}, splitEditor ? freeGroup : position);
			}
		}

		function positionOverlay(e: DragEvent, groups: number, position: Position): void {
			const target = <HTMLElement>e.target;
			const overlayIsSplit = typeof overlay.getProperty(splitToPropertyKey) === 'number';
			const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
			const draggedEditor = TitleControl.getDraggedEditor();

			const overlaySize = $this.layoutVertically ? target.clientWidth : target.clientHeight;
			const splitThreshold = overlayIsSplit ? overlaySize / 5 : overlaySize / 10;

			const posOnOverlay = $this.layoutVertically ? e.offsetX : e.offsetY;
			const isOverSplitLeftOrUp = posOnOverlay < splitThreshold;
			const isOverSplitRightOrBottom = posOnOverlay + splitThreshold > overlaySize;

			let splitTarget: Position;

			// No splitting if we reached maximum group count
			if (groups === POSITIONS.length) {
				splitTarget = null;
			}

			// Special splitting if we drag an editor of a group with only one editor
			else if (!isCopy && draggedEditor && draggedEditor.group.count === 1) {
				const positionOfDraggedEditor = stacks.positionOfGroup(draggedEditor.group);
				switch (positionOfDraggedEditor) {
					case Position.ONE:
						if (position === Position.TWO && isOverSplitRightOrBottom) {
							splitTarget = Position.TWO; // allow to move single editor from ONE to TWO
						}
						break;
					case Position.TWO:
						if (position === Position.ONE && isOverSplitLeftOrUp) {
							splitTarget = Position.ONE; // allow to move single editor from TWO to ONE
						}
						break;
					default:
						splitTarget = null; // splitting not allowed
				}
			}

			// Any other case, check for mouse position
			else {
				if (isOverSplitRightOrBottom) {
					splitTarget = (position === Position.ONE) ? Position.TWO : Position.THREE;
				} else if (isOverSplitLeftOrUp) {
					splitTarget = (position === Position.ONE) ? Position.ONE : Position.TWO;
				}
			}

			// Apply split target
			const canSplit = (typeof splitTarget === 'number');
			if (canSplit) {
				overlay.setProperty(splitToPropertyKey, splitTarget);
			} else {
				overlay.removeProperty(splitToPropertyKey);
			}

			// Update overlay styles
			if (canSplit && isOverSplitRightOrBottom) {
				overlay.style($this.layoutVertically ? { left: '50%', width: '50%' } : { top: '50%', height: '50%' });
			} else if (canSplit && isOverSplitLeftOrUp) {
				overlay.style($this.layoutVertically ? { width: '50%' } : { height: '50%' });
			} else {
				if ($this.layoutVertically) {
					overlay.style({ left: '0', width: '100%' });
				} else {
					overlay.style({ top: $this.tabOptions.showTabs ? `${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px` : 0, height: $this.tabOptions.showTabs ? `calc(100% - ${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px` : '100%' });
				}
			}

			// Make sure the overlay is visible
			overlay.style({ opacity: 1 });

			// Indicate a drag over is happening
			POSITIONS.forEach(p => {
				if (p === position) {
					$this.silos[p].addClass('dragged-over');
				} else {
					$this.silos[p].removeClass('dragged-over');
				}
			});
		}

		function createOverlay(target: HTMLElement): void {
			if (!overlay) {
				const containers = $this.visibleEditors.filter(e => !!e).map(e => e.getContainer());
				containers.forEach((container, index) => {
					if (container && DOM.isAncestor(target, container.getHTMLElement())) {
						const activeContrastBorderColor = $this.getColor(activeContrastBorder);
						overlay = $('div').style({
							top: $this.tabOptions.showTabs ? `${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px` : 0,
							height: $this.tabOptions.showTabs ? `calc(100% - ${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px` : '100%',
							backgroundColor: $this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND),
							outlineColor: activeContrastBorderColor,
							outlineOffset: activeContrastBorderColor ? '-2px' : null,
							outlineStyle: activeContrastBorderColor ? 'dashed' : null,
							outlineWidth: activeContrastBorderColor ? '2px' : null
						}).id(overlayId);

						overlay.appendTo(container);

						overlay.on(DOM.EventType.DROP, (e: DragEvent) => {
							DOM.EventHelper.stop(e, true);
							onDrop(e, index, overlay.getProperty(splitToPropertyKey));
						});

						overlay.on(DOM.EventType.DRAG_OVER, (e: DragEvent) => {

							// update the dropEffect, otherwise it would look like a "move" operation. but only if we are
							// not dragging a tab actually because there we support both moving as well as copying
							if (!TabsTitleControl.getDraggedEditor()) {
								e.dataTransfer.dropEffect = 'copy';
							}

							positionOverlay(e, containers.length, index);
						});

						overlay.on([DOM.EventType.DRAG_LEAVE, DOM.EventType.DRAG_END], () => {
							cleanUp();
						});

						// Under some circumstances we have seen reports where the drop overlay is not being
						// cleaned up and as such the editor area remains under the overlay so that you cannot
						// type into the editor anymore. This seems related to using VMs and DND via host and
						// guest OS, though some users also saw it without VMs.
						// To protect against this issue we always destroy the overlay as soon as we detect a
						// mouse event over it. The delay is used to guarantee we are not interfering with the
						// actual DROP event that can also trigger a mouse over event.
						overlay.once(DOM.EventType.MOUSE_OVER, () => {
							setTimeout(() => {
								cleanUp();
							}, 300);
						});
					}
				});
			}
		}

		// let a dropped file open inside Code (only if dropped over editor area)
		this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DROP, (e: DragEvent) => {
			if (e.target === node || DOM.isAncestor(e.target as HTMLElement, node)) {
				DOM.EventHelper.stop(e, true);
				onDrop(e, Position.ONE);
			} else {
				this.updateFromDragAndDrop(node, false);
			}
		}));

		// Drag enter
		let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
		this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
			if (!TitleControl.getDraggedEditor()) {
				// we used to check for the dragged resources here (via dnd.extractResources()) but this
				// seems to be not possible on Linux and Windows where during DRAG_ENTER the resources
				// are always undefined up until they are dropped when dragged from the tree. The workaround
				// is to check for a datatransfer type being set. See https://github.com/Microsoft/vscode/issues/25789
				if (!e.dataTransfer.types.length) {
					return; // invalid DND
				}
			}

			counter++;
			this.updateFromDragAndDrop(node, true);

			const target = <HTMLElement>e.target;
			if (target) {
				if (overlay && target.id !== overlayId) {
					cleanUp(); // somehow we managed to move the mouse quickly out of the current overlay, so destroy it
				}
				createOverlay(target);

				if (overlay) {
					this.updateFromDragAndDrop(node, false); // if we show an overlay, we can remove the drop feedback from the editor background
				}
			}
		}));

		// Drag leave
		this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
			counter--;
			if (counter === 0) {
				this.updateFromDragAndDrop(node, false);
			}
		}));

		// Drag end (also install globally to be safe)
		[node, window].forEach(container => {
			this.toUnbind.push(DOM.addDisposableListener(container, DOM.EventType.DRAG_END, (e: DragEvent) => {
				counter = 0;
				this.updateFromDragAndDrop(node, false);
				cleanUp();
			}));
		});
	}

	private createTitleControl(context: IEditorGroup, silo: Builder, container: Builder, instantiationService: IInstantiationService): void {
		const titleAreaControl = instantiationService.createInstance<ITitleAreaControl>(this.tabOptions.showTabs ? TabsTitleControl : NoTabsTitleControl);
		titleAreaControl.create(container.getHTMLElement());
		titleAreaControl.setContext(context);
		titleAreaControl.refresh(true /* instant */);

		silo.child().setProperty(EditorGroupsControl.TITLE_AREA_CONTROL_KEY, titleAreaControl); // associate with container
	}

	private findPosition(element: HTMLElement): Position {
		let parent = element.parentElement;
		while (parent) {
			for (let i = 0; i < POSITIONS.length; i++) {
				const position = POSITIONS[i];
				if (this.silos[position].getHTMLElement() === parent) {
					return position;
				}
			}

			parent = parent.parentElement;
		}

		return null;
	}

	private hookTitleDragListener(titleContainer: Builder): void {
		let wasDragged = false;

		// Allow to reorder positions by dragging the title
		titleContainer.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
			const position = this.findPosition(titleContainer.getHTMLElement());
			const titleAreaControl = this.getTitleAreaControl(position);
			if (!titleAreaControl.allowDragging((e.target || e.srcElement) as HTMLElement)) {
				return; // return early if we are not in the drag zone of the title widget
			}

			// Reset flag
			wasDragged = false;
			titleAreaControl.setDragged(false);

			// Return early if there is only one editor active or the user clicked into the toolbar
			if (this.getVisibleEditorCount() <= 1) {
				return;
			}

			// Only allow for first mouse button click!
			if (e.button !== 0) {
				return;
			}

			DOM.EventHelper.stop(e);

			// Overlay the editor area with a div to be able to capture all mouse events
			// Do NOT cover the title area to prevent missing double click events!
			const overlayDiv = $('div').style({
				top: `${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px`,
				height: `calc(100% - ${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px)`
			}).id('monaco-workbench-editor-move-overlay');
			overlayDiv.appendTo(this.silos[position]);

			// Update flag
			this.dragging = true;

			const visibleEditorCount = this.getVisibleEditorCount();
			const mouseDownEvent = new StandardMouseEvent(e);
			const startPos = this.layoutVertically ? mouseDownEvent.posx : mouseDownEvent.posy;
			let oldNewPos: number = null;

			this.silos[position].addClass('drag');

			const $window = $(window);
			$window.on(DOM.EventType.MOUSE_MOVE, (e: MouseEvent) => {
				DOM.EventHelper.stop(e, false);

				const mouseMoveEvent = new StandardMouseEvent(e);
				const diffPos = (this.layoutVertically ? mouseMoveEvent.posx : mouseMoveEvent.posy) - startPos;
				let newPos: number = null;

				if (Math.abs(diffPos) > 5) {
					wasDragged = true;
				}

				switch (position) {

					// [ ! ]|[ ]: Moves only to the right/bottom but not outside of dimension to the right/bottom
					case Position.ONE: {
						newPos = Math.max(-1 /* 1px border accomodation */, Math.min(diffPos, this.totalSize - this.silosSize[Position.ONE]));
						break;
					}

					case Position.TWO: {

						// [ ]|[ ! ]: Moves only to the left/top but not outside of dimension to the left/top
						if (visibleEditorCount === 2) {
							newPos = Math.min(this.silosSize[Position.ONE], Math.max(-1 /* 1px border accomodation */, this.silosSize[Position.ONE] + diffPos));
						}

						// [ ]|[ ! ]|[ ]: Moves to left/top and right/bottom but not outside of dimensions on both sides
						else {
							newPos = Math.min(this.totalSize - this.silosSize[Position.TWO], Math.max(-1 /* 1px border accomodation */, this.silosSize[Position.ONE] + diffPos));
						}
						break;
					}

					// [ ]|[ ]|[ ! ]: Moves to the right/bottom but not outside of dimension on the left/top side
					case Position.THREE: {
						newPos = Math.min(this.silosSize[Position.ONE] + this.silosSize[Position.TWO], Math.max(-1 /* 1px border accomodation */, this.silosSize[Position.ONE] + this.silosSize[Position.TWO] + diffPos));
						break;
					}
				}

				// Return early if position did not change
				if (oldNewPos === newPos) {
					return;
				}

				oldNewPos = newPos;

				// Live drag Feedback
				const moveTo: Position = this.findMoveTarget(position, diffPos);
				switch (position) {
					case Position.ONE: {
						if (moveTo === Position.ONE || moveTo === null) {
							this.posSilo(Position.TWO, `${this.silosSize[Position.ONE]}px`, 'auto', '1px');
							this.posSilo(Position.THREE, 'auto', 0);
						} else if (moveTo === Position.TWO) {
							this.posSilo(Position.TWO, 0, 'auto', 0);
							this.silos[Position.TWO].addClass('draggedunder');
							this.posSilo(Position.THREE, 'auto', 0);
						} else if (moveTo === Position.THREE) {
							this.posSilo(Position.TWO, 0, 'auto');
							this.posSilo(Position.THREE, 'auto', `${this.silosSize[Position.ONE]}px`);
							this.silos[Position.THREE].addClass('draggedunder');
						}
						break;
					}

					case Position.TWO: {
						if (moveTo === Position.ONE) {
							this.posSilo(Position.ONE, `${this.silosSize[Position.TWO]}px`, 'auto');
							this.silos[Position.ONE].addClass('draggedunder');
						} else if (moveTo === Position.TWO || moveTo === null) {
							this.posSilo(Position.ONE, 0, 'auto');
							this.posSilo(Position.THREE, 'auto', 0);
						} else if (moveTo === Position.THREE) {
							this.posSilo(Position.THREE, 'auto', `${this.silosSize[Position.TWO]}px`);
							this.silos[Position.THREE].addClass('draggedunder');
							this.posSilo(Position.ONE, 0, 'auto');
						}
						break;
					}

					case Position.THREE: {
						if (moveTo === Position.ONE) {
							this.posSilo(Position.ONE, `${this.silosSize[Position.THREE]}px`, 'auto');
							this.silos[Position.ONE].addClass('draggedunder');
						} else if (moveTo === Position.TWO) {
							this.posSilo(Position.ONE, 0, 'auto');
							this.posSilo(Position.TWO, `${this.silosSize[Position.ONE] + this.silosSize[Position.THREE]}px`, 'auto');
							this.silos[Position.TWO].addClass('draggedunder');
						} else if (moveTo === Position.THREE || moveTo === null) {
							this.posSilo(Position.ONE, 0, 'auto');
							this.posSilo(Position.TWO, `${this.silosSize[Position.ONE]}px`, 'auto');
						}
						break;
					}
				}

				// Move the editor to provide feedback to the user and add class
				if (newPos !== null) {
					this.posSilo(position, `${newPos}px`);
					this.updateFromDragging(position, true);
				}
			}).once(DOM.EventType.MOUSE_UP, (e: MouseEvent) => {
				DOM.EventHelper.stop(e, false);

				// Destroy overlay
				overlayDiv.destroy();

				// Update flag
				this.dragging = false;
				if (wasDragged) {
					titleAreaControl.setDragged(true);
				}

				// Restore styles
				this.silos[position].removeClass('drag');
				this.updateFromDragging(position, false);
				POSITIONS.forEach(p => this.silos[p].removeClass('draggedunder'));

				this.posSilo(Position.ONE, 0, 'auto');
				this.posSilo(Position.TWO, 'auto', 'auto', '1px');
				this.posSilo(Position.THREE, 'auto', 0);

				// Find move target
				const mouseUpEvent = new StandardMouseEvent(e);
				const diffPos = (this.layoutVertically ? mouseUpEvent.posx : mouseUpEvent.posy) - startPos;
				const moveTo: Position = this.findMoveTarget(position, diffPos);

				// Move to valid position if any
				if (moveTo !== null) {
					// TODO@Ben remove me after a while
					/* __GDPR__
						"editorGroupMoved" : {
							"source" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
							"to": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
						}
					*/
					this.telemetryService.publicLog('editorGroupMoved', { source: position, to: moveTo });
					this.editorGroupService.moveGroup(position, moveTo);
				}

				// Otherwise layout to restore proper positioning
				else {
					this.layoutContainers();
				}

				// If not dragging, make editor group active unless already active
				if (!wasDragged && position !== this.getActivePosition()) {
					this.editorGroupService.focusGroup(position);
				}

				$window.off('mousemove');
			});
		});
	}

	private updateFromDragging(position: Position, isDragging: boolean): void {
		const silo = this.silos[position];
		if (silo.hasClass('dragging') === isDragging) {
			return; // avoid repeated work
		}

		let borderColor = null;
		if (isDragging) {
			this.parent.addClass('dragging');
			silo.addClass('dragging');
			borderColor = this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder);
		} else {
			this.parent.removeClass('dragging');
			silo.removeClass('dragging');
		}

		silo.style(this.layoutVertically ? 'border-left-color' : 'border-top-color', borderColor);
		silo.style(this.layoutVertically ? 'border-right-color' : 'border-bottom-color', borderColor);

		// Back to normal styles once dragging stops
		if (!isDragging) {
			this.updateStyles();
		}
	}

	private updateFromDragAndDrop(element: HTMLElement, isDraggedOver: boolean): void {
		const groupCount = this.stacks.groups.length;
		const background = this.getColor(isDraggedOver ? EDITOR_DRAG_AND_DROP_BACKGROUND : groupCount > 0 ? EDITOR_GROUP_BACKGROUND : null);
		element.style.backgroundColor = background;

		const activeContrastBorderColor = this.getColor(activeContrastBorder);
		element.style.outlineColor = isDraggedOver ? activeContrastBorderColor : null;
		element.style.outlineStyle = isDraggedOver && activeContrastBorderColor ? 'dashed' : null;
		element.style.outlineWidth = isDraggedOver && activeContrastBorderColor ? '2px' : null;
		element.style.outlineOffset = isDraggedOver && activeContrastBorderColor ? '-2px' : null;

		DOM.toggleClass(element, 'dragged-over', isDraggedOver);
	}

	private posSilo(pos: number, leftTop: string | number, rightBottom?: string | number, borderLeftTopWidth?: string | number): void {
		let style: any;
		if (this.layoutVertically) {
			style = { left: leftTop };

			if (typeof rightBottom === 'number' || typeof rightBottom === 'string') {
				style['right'] = rightBottom;
			}

			if (typeof borderLeftTopWidth === 'number' || typeof borderLeftTopWidth === 'string') {
				style['borderLeftWidth'] = borderLeftTopWidth;
			}
		} else {
			style = { top: leftTop };

			if (typeof rightBottom === 'number' || typeof rightBottom === 'string') {
				style['bottom'] = rightBottom;
			}

			if (typeof borderLeftTopWidth === 'number' || typeof borderLeftTopWidth === 'string') {
				style['borderTopWidth'] = borderLeftTopWidth;
			}
		}

		this.silos[pos].style(style);
	}

	private findMoveTarget(position: Position, diffPos: number): Position {
		const visibleEditorCount = this.getVisibleEditorCount();

		switch (position) {
			case Position.ONE: {

				// [ ! ]|[] -> []|[ ! ]
				if (visibleEditorCount === 2 && (diffPos >= this.silosSize[Position.ONE] / 2 || diffPos >= this.silosSize[Position.TWO] / 2)) {
					return Position.TWO;
				}

				// [ ! ]|[]|[] -> []|[]|[ ! ]
				if (visibleEditorCount === 3 && (diffPos >= this.silosSize[Position.ONE] / 2 + this.silosSize[Position.TWO] || diffPos >= this.silosSize[Position.THREE] / 2 + this.silosSize[Position.TWO])) {
					return Position.THREE;
				}

				// [ ! ]|[]|[] -> []|[ ! ]|[]
				if (visibleEditorCount === 3 && (diffPos >= this.silosSize[Position.ONE] / 2 || diffPos >= this.silosSize[Position.TWO] / 2)) {
					return Position.TWO;
				}
				break;
			}

			case Position.TWO: {
				if (visibleEditorCount === 2 && diffPos > 0) {
					return null; // Return early since TWO cannot be moved to the THREE unless there is a THREE position
				}

				// []|[ ! ] -> [ ! ]|[]
				if (visibleEditorCount === 2 && (Math.abs(diffPos) >= this.silosSize[Position.TWO] / 2 || Math.abs(diffPos) >= this.silosSize[Position.ONE] / 2)) {
					return Position.ONE;
				}

				// []|[ ! ]|[] -> [ ! ]|[]|[]
				if (visibleEditorCount === 3 && ((diffPos < 0 && Math.abs(diffPos) >= this.silosSize[Position.TWO] / 2) || (diffPos < 0 && Math.abs(diffPos) >= this.silosSize[Position.ONE] / 2))) {
					return Position.ONE;
				}

				// []|[ ! ]|[] -> []|[]|[ ! ]
				if (visibleEditorCount === 3 && ((diffPos > 0 && Math.abs(diffPos) >= this.silosSize[Position.TWO] / 2) || (diffPos > 0 && Math.abs(diffPos) >= this.silosSize[Position.THREE] / 2))) {
					return Position.THREE;
				}
				break;
			}

			case Position.THREE: {
				if (diffPos > 0) {
					return null; // Return early since THREE cannot be moved more to the THREE
				}

				// []|[]|[ ! ] -> [ ! ]|[]|[]
				if (Math.abs(diffPos) >= this.silosSize[Position.THREE] / 2 + this.silosSize[Position.TWO] || Math.abs(diffPos) >= this.silosSize[Position.ONE] / 2 + this.silosSize[Position.TWO]) {
					return Position.ONE;
				}

				// []|[]|[ ! ] -> []|[ ! ]|[]
				if (Math.abs(diffPos) >= this.silosSize[Position.THREE] / 2 || Math.abs(diffPos) >= this.silosSize[Position.TWO] / 2) {
					return Position.TWO;
				}
				break;
			}
		}

		return null;
	}

	private centerSash(a: Position, b: Position): void {
		const sumSize = this.silosSize[a] + this.silosSize[b];
		const meanSize = sumSize / 2;
		this.silosSize[a] = meanSize;
		this.silosSize[b] = sumSize - meanSize;

		this.layoutContainers();
	}

	private onSashOneDragStart(): void {
		this.startSiloOneSize = this.silosSize[Position.ONE];
	}

	private onSashOneDrag(e: ISashEvent): void {
		let oldSiloOneSize = this.silosSize[Position.ONE];
		let diffSize = this.layoutVertically ? (e.currentX - e.startX) : (e.currentY - e.startY);
		let newSiloOneSize = this.startSiloOneSize + diffSize;

		// Side-by-Side
		if (this.sashTwo.isHidden()) {

			// []|[      ] : left/top side can not get smaller than the minimal editor size
			if (newSiloOneSize < this.minSize) {
				newSiloOneSize = this.minSize;
			}

			// [      ]|[] : right/bottom side can not get smaller than the minimal editor size
			else if (this.totalSize - newSiloOneSize < this.minSize) {
				newSiloOneSize = this.totalSize - this.minSize;
			}

			// [ <-]|[      ] : left/top side can snap into minimized
			else if (newSiloOneSize - this.snapToMinimizeThresholdSize <= this.minSize) {
				newSiloOneSize = this.minSize;
			}

			// [      ]|[-> ] : right/bottom side can snap into minimized
			else if (this.totalSize - newSiloOneSize - this.snapToMinimizeThresholdSize <= this.minSize) {
				newSiloOneSize = this.totalSize - this.minSize;
			}

			this.silosSize[Position.ONE] = newSiloOneSize;
			this.silosSize[Position.TWO] = this.totalSize - newSiloOneSize;
		}

		// Side-by-Side-by-Side
		else {

			// [!]|[      ]|[  ] : left/top side can not get smaller than the minimal editor size
			if (newSiloOneSize < this.minSize) {
				newSiloOneSize = this.minSize;
			}

			// [      ]|[!]|[  ] : center side can not get smaller than the minimal editor size
			else if (this.totalSize - newSiloOneSize - this.silosSize[Position.THREE] < this.minSize) {

				// [      ]|[ ]|[!] : right/bottom side can not get smaller than the minimal editor size
				if (this.totalSize - newSiloOneSize - this.silosSize[Position.TWO] < this.minSize) {
					newSiloOneSize = this.totalSize - (2 * this.minSize);
					this.silosSize[Position.TWO] = this.silosSize[Position.THREE] = this.minSize;
				}

				// [      ]|[ ]|[-> ] : right/bottom side can snap into minimized
				else if (this.totalSize - newSiloOneSize - this.silosSize[Position.TWO] - this.snapToMinimizeThresholdSize <= this.minSize) {
					this.silosSize[Position.THREE] = this.minSize;
				}

				// [      ]|[ ]|[ ] : right/bottom side shrinks
				else {
					this.silosSize[Position.THREE] = this.silosSize[Position.THREE] - (newSiloOneSize - oldSiloOneSize);
				}

				this.sashTwo.layout();
			}

			// [ <-]|[      ]|[  ] : left/top side can snap into minimized
			else if (newSiloOneSize - this.snapToMinimizeThresholdSize <= this.minSize) {
				newSiloOneSize = this.minSize;
			}

			// [      ]|[-> ]|[  ] : center side can snap into minimized
			else if (this.totalSize - this.silosSize[Position.THREE] - newSiloOneSize - this.snapToMinimizeThresholdSize <= this.minSize) {
				newSiloOneSize = this.totalSize - this.silosSize[Position.THREE] - this.minSize;
			}

			this.silosSize[Position.ONE] = newSiloOneSize;
			this.silosSize[Position.TWO] = this.totalSize - this.silosSize[Position.ONE] - this.silosSize[Position.THREE];
		}

		// We allow silos to turn into minimized state from user dragging the sash,
		// so we need to update our stored state of minimized silos accordingly
		this.enableMinimizedState();

		// Pass on to containers
		this.layoutContainers();
	}

	private onSashOneDragEnd(): void {
		this.sashOne.layout();
		this.sashTwo.layout(); // Moving sash one might have also moved sash two, so layout() both
		this.focusNextNonMinimized();
	}

	private onSashOneReset(): void {
		this.centerSash(Position.ONE, Position.TWO);
		this.sashOne.layout();
	}

	private onSashTwoDragStart(): void {
		this.startSiloThreeSize = this.silosSize[Position.THREE];
	}

	private onSashTwoDrag(e: ISashEvent): void {
		let oldSiloThreeSize = this.silosSize[Position.THREE];
		let diffSize = this.layoutVertically ? (-e.currentX + e.startX) : (-e.currentY + e.startY);
		let newSiloThreeSize = this.startSiloThreeSize + diffSize;

		// [  ]|[      ]|[!] : right/bottom side can not get smaller than the minimal editor size
		if (newSiloThreeSize < this.minSize) {
			newSiloThreeSize = this.minSize;
		}

		// [      ]|[!]|[  ] : center side can not get smaller than the minimal editor size
		else if (this.totalSize - newSiloThreeSize - this.silosSize[Position.ONE] < this.minSize) {

			// [!]|[ ]|[    ] : left/top side can not get smaller than the minimal editor size
			if (this.totalSize - newSiloThreeSize - this.silosSize[Position.TWO] < this.minSize) {
				newSiloThreeSize = this.totalSize - (2 * this.minSize);
				this.silosSize[Position.ONE] = this.silosSize[Position.TWO] = this.minSize;
			}

			// [ <-]|[ ]|[    ] : left/top side can snap into minimized
			else if (this.totalSize - newSiloThreeSize - this.silosSize[Position.TWO] - this.snapToMinimizeThresholdSize <= this.minSize) {
				this.silosSize[Position.ONE] = this.minSize;
			}

			// [  ]|[ ]|[   ] : left/top side shrinks
			else {
				this.silosSize[Position.ONE] = this.silosSize[Position.ONE] - (newSiloThreeSize - oldSiloThreeSize);
			}

			this.sashOne.layout();
		}

		// [ ]|[      ]|[-> ] : right/bottom side can snap into minimized
		else if (newSiloThreeSize - this.snapToMinimizeThresholdSize <= this.minSize) {
			newSiloThreeSize = this.minSize;
		}

		// [ ]|[ <-]|[      ] : center side can snap into minimized
		else if (this.totalSize - this.silosSize[Position.ONE] - newSiloThreeSize - this.snapToMinimizeThresholdSize <= this.minSize) {
			newSiloThreeSize = this.totalSize - this.silosSize[Position.ONE] - this.minSize;
		}

		this.silosSize[Position.THREE] = newSiloThreeSize;
		this.silosSize[Position.TWO] = this.totalSize - this.silosSize[Position.ONE] - this.silosSize[Position.THREE];

		// We allow silos to turn into minimized state from user dragging the sash,
		// so we need to update our stored state of minimized silos accordingly
		this.enableMinimizedState();

		// Pass on to containers
		this.layoutContainers();
	}

	private onSashTwoDragEnd(): void {
		this.sashOne.layout(); // Moving sash one might have also moved sash two, so layout() both
		this.sashTwo.layout();

		this.focusNextNonMinimized();
	}

	private onSashTwoReset(): void {
		this.centerSash(Position.TWO, Position.THREE);

		this.sashTwo.layout();
	}

	public getVerticalSashTop(sash: Sash): number {
		return 0;
	}

	public getVerticalSashLeft(sash: Sash): number {
		return sash === this.sashOne ? this.silosSize[Position.ONE] : this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
	}

	public getVerticalSashHeight(sash: Sash): number {
		return this.dimension.height;
	}

	public getHorizontalSashTop(sash: Sash): number {
		return sash === this.sashOne ? this.silosSize[Position.ONE] : this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
	}

	public getHorizontalSashLeft(sash: Sash): number {
		return 0;
	}

	public getHorizontalSashWidth(sash: Sash): number {
		return this.dimension.width;
	}

	public isDragging(): boolean {
		return this.dragging;
	}

	public layout(dimension: Dimension): void;
	public layout(position: Position): void;
	public layout(arg: any): void {
		if (arg instanceof Dimension) {
			this.layoutControl(<Dimension>arg);
		} else {
			this.layoutEditor(<Position>arg);
		}
	}

	private layoutControl(dimension: Dimension): void {
		let oldDimension = this.dimension;
		this.dimension = dimension;

		// Use the current dimension in case an editor was opened before we had any dimension
		if (!oldDimension || !oldDimension.width || !oldDimension.height) {
			oldDimension = dimension;
		}

		// Apply to visible editors
		let totalSize = 0;

		// Set preferred dimensions based on ratio to previous dimenions
		let wasInitialRatioRestored = false;
		const oldTotalSize = this.layoutVertically ? oldDimension.width : oldDimension.height;
		POSITIONS.forEach(position => {
			if (this.visibleEditors[position]) {

				// Keep minimized editors in tact by not letting them grow if we have size to give
				if (!this.isSiloMinimized(position)) {
					let siloSizeRatio: number;

					// We have some stored initial ratios when the editor was restored on startup
					// Use those ratios over anything else but only once.
					if (this.silosInitialRatio && types.isNumber(this.silosInitialRatio[position])) {
						siloSizeRatio = this.silosInitialRatio[position];
						delete this.silosInitialRatio[position]; // dont use again
						wasInitialRatioRestored = true;
					} else {
						siloSizeRatio = this.silosSize[position] / oldTotalSize;
					}

					this.silosSize[position] = Math.max(Math.round(this.totalSize * siloSizeRatio), this.minSize);
				}

				totalSize += this.silosSize[position];
			}
		});

		// When restoring from an initial ratio state, we treat editors of min-size as
		// minimized, so we need to update our stored state of minimized silos accordingly
		if (wasInitialRatioRestored) {
			this.enableMinimizedState();
		}

		// Compensate for overflow either through rounding error or min editor size
		if (totalSize > 0) {
			let overflow = totalSize - this.totalSize;

			// We have size to give
			if (overflow < 0) {

				// Find the first position from left/top to right/bottom that is not minimized
				// to give size. This ensures that minimized editors are left like
				// that if the user chose this layout.
				let positionToGive: Position = null;
				POSITIONS.forEach(position => {
					if (this.visibleEditors[position] && positionToGive === null && !this.isSiloMinimized(position)) {
						positionToGive = position;
					}
				});

				if (positionToGive === null) {
					positionToGive = Position.ONE; // maybe all are minimized, so give ONE the extra size
				}

				this.silosSize[positionToGive] -= overflow;
			}

			// We have size to take
			else if (overflow > 0) {
				POSITIONS.forEach(position => {
					const maxCompensation = this.silosSize[position] - this.minSize;
					if (maxCompensation >= overflow) {
						this.silosSize[position] -= overflow;
						overflow = 0;
					} else if (maxCompensation > 0) {
						this.silosSize[position] -= maxCompensation;
						overflow -= maxCompensation;
					}
				});
			}
		}

		// Sash positioning
		this.sashOne.layout();
		this.sashTwo.layout();

		// Pass on to Editor Containers
		this.layoutContainers();
	}

	private layoutContainers(): void {

		// Layout containers
		POSITIONS.forEach(position => {
			const siloWidth = this.layoutVertically ? this.silosSize[position] : this.dimension.width;
			const siloHeight = this.layoutVertically ? this.dimension.height : this.silosSize[position];

			this.silos[position].size(siloWidth, siloHeight);
		});

		if (this.layoutVertically) {
			this.silos[Position.TWO].position(0, null, null, this.silosSize[Position.ONE]);
		} else {
			this.silos[Position.TWO].position(this.silosSize[Position.ONE], null, null, 0);
		}

		// Visibility
		POSITIONS.forEach(position => {
			if (this.visibleEditors[position] && this.silos[position].isHidden()) {
				this.silos[position].show();
			} else if (!this.visibleEditors[position] && !this.silos[position].isHidden()) {
				this.silos[position].hide();
			}
		});

		// Layout visible editors
		POSITIONS.forEach(position => {
			this.layoutEditor(position);
		});

		// Layout title controls
		POSITIONS.forEach(position => {
			const siloWidth = this.layoutVertically ? this.silosSize[position] : this.dimension.width;

			this.getTitleAreaControl(position).layout(new Dimension(siloWidth, EditorGroupsControl.EDITOR_TITLE_HEIGHT));
		});

		// Update minimized state
		this.updateMinimizedState();
	}

	private layoutEditor(position: Position): void {
		const editorSize = this.silosSize[position];
		if (editorSize && this.visibleEditors[position]) {
			let editorWidth = this.layoutVertically ? editorSize : this.dimension.width;
			let editorHeight = (this.layoutVertically ? this.dimension.height : this.silosSize[position]) - EditorGroupsControl.EDITOR_TITLE_HEIGHT;

			if (position !== Position.ONE) {
				if (this.layoutVertically) {
					editorWidth--; // accomodate for 1px left-border in containers TWO, THREE when laying out vertically
				} else {
					editorHeight--; // accomodate for 1px top-border in containers TWO, THREE when laying out horizontally
				}
			}

			this.visibleEditors[position].layout(new Dimension(editorWidth, editorHeight));
		}
	}

	public getInstantiationService(position: Position): IInstantiationService {
		return this.getFromContainer(position, EditorGroupsControl.INSTANTIATION_SERVICE_KEY);
	}

	public getProgressBar(position: Position): ProgressBar {
		return this.getFromContainer(position, EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY);
	}

	private getTitleAreaControl(position: Position): ITitleAreaControl {
		return this.getFromContainer(position, EditorGroupsControl.TITLE_AREA_CONTROL_KEY);
	}

	private getFromContainer(position: Position, key: string): any {
		const silo = this.silos[position];

		return silo ? silo.child().getProperty(key) : void 0;
	}

	public updateTitleAreas(refreshActive?: boolean): void {
		POSITIONS.forEach(position => {
			const group = this.stacks.groupAt(position);
			if (!group) {
				return;
			}

			const titleControl = this.getTitleAreaControl(position);
			if (!titleControl) {
				return;
			}

			// Make sure the active group is shown in the title
			// and refresh it if we are instructed to refresh it
			if (refreshActive && group.isActive) {
				titleControl.setContext(group);
				titleControl.refresh(true);
			}

			// Otherwise, just refresh the toolbar
			else {
				titleControl.updateEditorActionsToolbar();
			}
		});
	}

	public updateProgress(position: Position, state: ProgressState): void {
		const progressbar = this.getProgressBar(position);
		if (!progressbar) {
			return;
		}

		switch (state) {
			case ProgressState.INFINITE:
				progressbar.infinite().getContainer().show();
				break;
			case ProgressState.DONE:
				progressbar.done().getContainer().hide();
				break;
			case ProgressState.STOP:
				progressbar.stop().getContainer().hide();
				break;
		}
	}

	public dispose(): void {
		super.dispose();

		// Positions
		POSITIONS.forEach(position => {
			this.clearPosition(position);
		});

		// Controls
		POSITIONS.forEach(position => {
			this.getTitleAreaControl(position).dispose();
			this.getProgressBar(position).dispose();
		});

		// Sash
		this.sashOne.dispose();
		this.sashTwo.dispose();

		// Destroy Container
		this.silos.forEach(silo => {
			silo.destroy();
		});

		this.lastActiveEditor = null;
		this.lastActivePosition = null;
		this.visibleEditors = null;
	}
}


text:
class ThreadPool {
	size_t thread_count;
	std::vector<std::thread> workers;
	std::queue<std::function<void()>> tasks;
	std::mutex queue_mutex;
	std::condition_variable condition;
	bool stop;

public:
	ThreadPool(size_t);
	template <typename F, typename... Args>
	auto enqueue(F&& f, Args&&... args)
			-> std::future<typename std::result_of<F(Args...)>::type>;
	~ThreadPool();
};

inline ThreadPool::ThreadPool(size_t threads) : thread_count(threads), stop(false) {
	for (size_t i = 0; i < threads; ++i) {
		workers.emplace_back(
				[this] {
					for (;;) {
						std::function<void()> task;

						{
							std::unique_lock<std::mutex> lock(this->queue_mutex);
							this->condition.wait(lock,
								[this]{ return this->stop || !this->tasks.empty(); });
							if (this->stop && this->tasks.empty())
								return;
							task = std::move(this->tasks.front());
							this->tasks.pop();
						}

						task();
					}
				}
		);
	}
}

inline ThreadPool::~ThreadPool() {
	{
		std::unique_lock<std::mutex> lock(queue_mutex);
		stop = true;
	}
	condition.notify_all();
	for (std::thread &worker: workers)
		worker.join();
}

#endif //PROJECT_THREADPOOL_H


text: ActionController::Base.class_eval { include Conversations }


text:
extern "C" {
#endif

void sgenrand(unsigned long seed);
float genrand();

#ifdef __cplusplus
}
#endif

#endif