package project

import (
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/libcompose/project/events"
)

type serviceWrapper struct {
	name    string
	service Service
	done    sync.WaitGroup
	state   ServiceState
	err     error
	project *Project
	noWait  bool
	ignored map[string]bool
}

func newServiceWrapper(name string, p *Project) (*serviceWrapper, error) {
	wrapper := &serviceWrapper{
		name:    name,
		state:   StateUnknown,
		project: p,
		ignored: map[string]bool{},
	}

	return wrapper, wrapper.Reset()
}

func (s *serviceWrapper) IgnoreDep(name string) {
	s.ignored[name] = true
}

func (s *serviceWrapper) Reset() error {
	if s.state != StateExecuted {
		service, err := s.project.CreateService(s.name)
		if err != nil {
			log.Errorf("Failed to create service for %s : %v", s.name, err)
			return err
		}

		s.service = service
	}

	if s.err == ErrRestart {
		s.err = nil
	}
	s.done.Add(1)

	return nil
}

func (s *serviceWrapper) Ignore() {
	defer s.done.Done()

	s.state = StateExecuted
	s.project.Notify(events.ServiceUpIgnored, s.service.Name(), nil)
}

func (s *serviceWrapper) waitForDeps(wrappers map[string]*serviceWrapper) bool {
	if s.noWait {
		return true
	}

	for _, dep := range s.service.DependentServices() {
		if s.ignored[dep.Target] {
			continue
		}

		if wrapper, ok := wrappers[dep.Target]; ok {
			if wrapper.Wait() == ErrRestart {
				s.project.Notify(events.ProjectReload, wrapper.service.Name(), nil)
				s.err = ErrRestart
				return false
			}
		} else {
			log.Errorf("Failed to find %s", dep.Target)
		}
	}

	return true
}

func (s *serviceWrapper) Do(wrappers map[string]*serviceWrapper, start, done events.EventType, action func(service Service) error) {
	defer s.done.Done()

	if s.state == StateExecuted {
		return
	}

	if wrappers != nil && !s.waitForDeps(wrappers) {
		return
	}

	s.state = StateExecuted

	s.project.Notify(start, s.service.Name(), nil)

	s.err = action(s.service)
	if s.err == ErrRestart {
		s.project.Notify(done, s.service.Name(), nil)
		s.project.Notify(events.ProjectReloadTrigger, s.service.Name(), nil)
	} else if s.err != nil {
		log.Errorf("Failed %s %s : %v", start, s.name, s.err)
	} else {
		s.project.Notify(done, s.service.Name(), nil)
	}
}

func (s *serviceWrapper) Wait() error {
	s.done.Wait()
	return s.err
}
