// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    crate::model::{
        actions::{Action, ActionKey, ActionSet, DiscoverAction},
        component::{
            Component, ComponentInstance, InstanceState, ResolvedInstanceState,
            WeakComponentInstance,
        },
        error::ModelError,
        hooks::{Event, EventError, EventErrorPayload, EventPayload},
        resolver::Resolver,
    },
    async_trait::async_trait,
    std::convert::TryFrom,
    std::sync::Arc,
};

/// Resolves a component instance's declaration and initializes its state.
pub struct ResolveAction {}

impl ResolveAction {
    pub fn new() -> Self {
        Self {}
    }
}

#[async_trait]
impl Action for ResolveAction {
    type Output = Result<Component, ModelError>;
    async fn handle(&self, component: &Arc<ComponentInstance>) -> Self::Output {
        do_resolve(component).await
    }
    fn key(&self) -> ActionKey {
        ActionKey::Resolve
    }
}

async fn do_resolve(component: &Arc<ComponentInstance>) -> Result<Component, ModelError> {
    // Ensure `Resolved` is dispatched after `Discovered`.
    ActionSet::register(component.clone(), DiscoverAction::new()).await?;
    let result = async move {
        let first_resolve = {
            let state = component.lock_state().await;
            match *state {
                InstanceState::New => {
                    panic!("Component should be at least discovered")
                }
                InstanceState::Discovered => true,
                InstanceState::Resolved(_) => false,
                InstanceState::Destroyed => {
                    return Err(ModelError::instance_not_found(component.abs_moniker.clone()));
                }
            }
        };
        let component_info = component
            .environment
            .resolve(&component.component_url)
            .await
            .map_err(|err| ModelError::from(err))?;
        let component_info = Component::try_from(component_info)?;
        if first_resolve {
            {
                let mut state = component.lock_state().await;
                match *state {
                    InstanceState::Resolved(_) => {
                        panic!("Component was marked Resolved during Resolve action?");
                    }
                    InstanceState::Destroyed => {
                        return Err(ModelError::instance_not_found(component.abs_moniker.clone()));
                    }
                    InstanceState::New | InstanceState::Discovered => {}
                }
                state.set(InstanceState::Resolved(
                    ResolvedInstanceState::new(component, component_info.decl.clone()).await,
                ));
            }
        }
        Ok((component_info, first_resolve))
    }
    .await;

    match result {
        Ok((component_info, false)) => Ok(component_info),
        Ok((component_info, true)) => {
            let event = Event::new(
                component,
                Ok(EventPayload::Resolved {
                    component: WeakComponentInstance::from(component),
                    resolved_url: component_info.resolved_url.clone(),
                    decl: component_info.decl.clone(),
                }),
            );
            component.hooks.dispatch(&event).await?;
            Ok(component_info)
        }
        Err(e) => {
            let event =
                Event::new(component, Err(EventError::new(&e, EventErrorPayload::Resolved)));
            component.hooks.dispatch(&event).await?;
            Err(e)
        }
    }
}
