// Copyright (c) 2013, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package storage

import (
	"bytes"
	"os"
	"sync"

	"github.com/ipfs/fs-repo-migrations/fs-repo-1-to-2/goleveldb/leveldb/util"
)

const typeShift = 3

type memStorageLock struct {
	ms *memStorage
}

func (lock *memStorageLock) Release() {
	ms := lock.ms
	ms.mu.Lock()
	defer ms.mu.Unlock()
	if ms.slock == lock {
		ms.slock = nil
	}
	return
}

// memStorage is a memory-backed storage.
type memStorage struct {
	mu       sync.Mutex
	slock    *memStorageLock
	files    map[uint64]*memFile
	manifest *memFilePtr
}

// NewMemStorage returns a new memory-backed storage implementation.
func NewMemStorage() Storage {
	return &memStorage{
		files: make(map[uint64]*memFile),
	}
}

func (ms *memStorage) Lock() (util.Releaser, error) {
	ms.mu.Lock()
	defer ms.mu.Unlock()
	if ms.slock != nil {
		return nil, ErrLocked
	}
	ms.slock = &memStorageLock{ms: ms}
	return ms.slock, nil
}

func (*memStorage) Log(str string) {}

func (ms *memStorage) GetFile(num uint64, t FileType) File {
	return &memFilePtr{ms: ms, num: num, t: t}
}

func (ms *memStorage) GetFiles(t FileType) ([]File, error) {
	ms.mu.Lock()
	var ff []File
	for x, _ := range ms.files {
		num, mt := x>>typeShift, FileType(x)&TypeAll
		if mt&t == 0 {
			continue
		}
		ff = append(ff, &memFilePtr{ms: ms, num: num, t: mt})
	}
	ms.mu.Unlock()
	return ff, nil
}

func (ms *memStorage) GetManifest() (File, error) {
	ms.mu.Lock()
	defer ms.mu.Unlock()
	if ms.manifest == nil {
		return nil, os.ErrNotExist
	}
	return ms.manifest, nil
}

func (ms *memStorage) SetManifest(f File) error {
	fm, ok := f.(*memFilePtr)
	if !ok || fm.t != TypeManifest {
		return ErrInvalidFile
	}
	ms.mu.Lock()
	ms.manifest = fm
	ms.mu.Unlock()
	return nil
}

func (*memStorage) Close() error { return nil }

type memReader struct {
	*bytes.Reader
	m *memFile
}

func (mr *memReader) Close() error {
	return mr.m.Close()
}

type memFile struct {
	bytes.Buffer
	ms   *memStorage
	open bool
}

func (*memFile) Sync() error { return nil }
func (m *memFile) Close() error {
	m.ms.mu.Lock()
	m.open = false
	m.ms.mu.Unlock()
	return nil
}

type memFilePtr struct {
	ms  *memStorage
	num uint64
	t   FileType
}

func (p *memFilePtr) x() uint64 {
	return p.Num()<<typeShift | uint64(p.Type())
}

func (p *memFilePtr) Open() (Reader, error) {
	ms := p.ms
	ms.mu.Lock()
	defer ms.mu.Unlock()
	if m, exist := ms.files[p.x()]; exist {
		if m.open {
			return nil, errFileOpen
		}
		m.open = true
		return &memReader{Reader: bytes.NewReader(m.Bytes()), m: m}, nil
	}
	return nil, os.ErrNotExist
}

func (p *memFilePtr) Create() (Writer, error) {
	ms := p.ms
	ms.mu.Lock()
	defer ms.mu.Unlock()
	m, exist := ms.files[p.x()]
	if exist {
		if m.open {
			return nil, errFileOpen
		}
		m.Reset()
	} else {
		m = &memFile{ms: ms}
		ms.files[p.x()] = m
	}
	m.open = true
	return m, nil
}

func (p *memFilePtr) Replace(newfile File) error {
	p1, ok := newfile.(*memFilePtr)
	if !ok {
		return ErrInvalidFile
	}
	ms := p.ms
	ms.mu.Lock()
	defer ms.mu.Unlock()
	m1, exist := ms.files[p1.x()]
	if !exist {
		return os.ErrNotExist
	}
	m0, exist := ms.files[p.x()]
	if (exist && m0.open) || m1.open {
		return errFileOpen
	}
	delete(ms.files, p1.x())
	ms.files[p.x()] = m1
	return nil
}

func (p *memFilePtr) Type() FileType {
	return p.t
}

func (p *memFilePtr) Num() uint64 {
	return p.num
}

func (p *memFilePtr) Remove() error {
	ms := p.ms
	ms.mu.Lock()
	defer ms.mu.Unlock()
	if _, exist := ms.files[p.x()]; exist {
		delete(ms.files, p.x())
		return nil
	}
	return os.ErrNotExist
}
