package mfs

import (
	"context"
	"fmt"
	"sync"

	chunk "github.com/ipfs/go-ipfs/importer/chunk"
	dag "github.com/ipfs/go-ipfs/merkledag"
	ft "github.com/ipfs/go-ipfs/unixfs"
	mod "github.com/ipfs/go-ipfs/unixfs/mod"
)

type File struct {
	parent childCloser

	name string

	desclock sync.RWMutex

	dserv  dag.DAGService
	node   *dag.Node
	nodelk sync.Mutex
}

// NewFile returns a NewFile object with the given parameters
func NewFile(name string, node *dag.Node, parent childCloser, dserv dag.DAGService) (*File, error) {
	return &File{
		dserv:  dserv,
		parent: parent,
		name:   name,
		node:   node,
	}, nil
}

const (
	OpenReadOnly = iota
	OpenWriteOnly
	OpenReadWrite
)

func (fi *File) Open(flags int, sync bool) (FileDescriptor, error) {
	fi.nodelk.Lock()
	node := fi.node
	fi.nodelk.Unlock()

	fsn, err := ft.FSNodeFromBytes(node.Data())
	if err != nil {
		return nil, err
	}

	switch fsn.Type {
	default:
		return nil, fmt.Errorf("unsupported fsnode type for 'file'")
	case ft.TSymlink:
		return nil, fmt.Errorf("symlinks not yet supported")
	case ft.TFile, ft.TRaw:
		// OK case
	}

	switch flags {
	case OpenReadOnly:
		fi.desclock.RLock()
	case OpenWriteOnly, OpenReadWrite:
		fi.desclock.Lock()
	default:
		// TODO: support other modes
		return nil, fmt.Errorf("mode not supported")
	}

	dmod, err := mod.NewDagModifier(context.TODO(), node, fi.dserv, chunk.DefaultSplitter)
	if err != nil {
		return nil, err
	}

	return &fileDescriptor{
		inode: fi,
		perms: flags,
		sync:  sync,
		mod:   dmod,
	}, nil
}

// Size returns the size of this file
func (fi *File) Size() (int64, error) {
	fi.nodelk.Lock()
	defer fi.nodelk.Unlock()
	pbd, err := ft.FromBytes(fi.node.Data())
	if err != nil {
		return 0, err
	}

	return int64(pbd.GetFilesize()), nil
}

// GetNode returns the dag node associated with this file
func (fi *File) GetNode() (*dag.Node, error) {
	fi.nodelk.Lock()
	defer fi.nodelk.Unlock()
	return fi.node, nil
}

func (fi *File) Flush() error {
	// open the file in fullsync mode
	fd, err := fi.Open(OpenWriteOnly, true)
	if err != nil {
		return err
	}

	defer fd.Close()

	return fd.Flush()
}

func (fi *File) Sync() error {
	// just being able to take the writelock means the descriptor is synced
	fi.desclock.Lock()
	fi.desclock.Unlock()
	return nil
}

// Type returns the type FSNode this is
func (fi *File) Type() NodeType {
	return TFile
}
