#include "computer.h"

#include <clocale>
#include <cstdlib>
#include <sstream>

#include "debug.h"
#include "enum_conversions.h"
#include "json.h"
#include "output.h"
#include "talker_furniture.h"
#include "translations.h"

template <typename E> struct enum_traits;

computer_option::computer_option()
    : name( "Unknown" ), action( COMPACT_NULL ), security( 0 )
{
}

computer_option::computer_option( const std::string &N, computer_action A, int S )
    : name( N ), action( A ), security( S )
{
}

void computer_option::serialize( JsonOut &jout ) const
{
    jout.start_object();
    jout.member( "name", name );
    jout.member( "action" );
    jout.write_as_string( action );
    jout.member( "security", security );
    jout.end_object();
}

void computer_option::deserialize( const JsonObject &jo )
{
    name = jo.get_string( "name" );
    action = jo.get_enum_value<computer_action>( "action" );
    security = jo.get_int( "security" );
}

computer_failure::computer_failure()
    : type( COMPFAIL_NULL )
{
}

void computer_failure::serialize( JsonOut &jout ) const
{
    jout.start_object();
    jout.member( "action" );
    jout.write_as_string( type );
    jout.end_object();
}

void computer_failure::deserialize( const JsonObject &jo )
{
    type = jo.get_enum_value<computer_failure_type>( "action" );
}

computer::computer( const std::string &new_name, int new_security, tripoint new_loc )
    : name( new_name ), mission_id( -1 ), security( new_security ), alerts( 0 ),
      next_attempt( calendar::before_time_starts ),
      access_denied( _( "ERROR!  Access denied!" ) )
{
    loc = new_loc;
}

void computer::set_security( int Security )
{
    security = Security;
}

void computer::add_option( const computer_option &opt )
{
    options.emplace_back( opt );
}

void computer::add_eoc( const effect_on_condition_id &eoc )
{
    eocs.emplace_back( eoc );
}

void computer::add_chat_topic( const std::string &topic )
{
    chat_topics.emplace_back( topic );
}

void computer::add_option( const std::string &opt_name, computer_action action,
                           int security )
{
    add_option( computer_option( opt_name, action, security ) );
}

void computer::add_failure( const computer_failure &failure )
{
    failures.emplace_back( failure );
}

void computer::add_failure( computer_failure_type failure )
{
    add_failure( computer_failure( failure ) );
}

void computer::set_access_denied_msg( const std::string &new_msg )
{
    access_denied = new_msg;
}

void computer::set_mission( const int id )
{
    mission_id = id;
}

// Methods for setting/getting misc key/value pairs.
void computer::set_value( const std::string &key, const std::string &value )
{
    values[ key ] = value;
}

void computer::remove_value( const std::string &key )
{
    values.erase( key );
}

std::string computer::get_value( const std::string &key ) const
{
    auto it = values.find( key );
    return ( it == values.end() ) ? "" : it->second;
}

static computer_action computer_action_from_legacy_enum( int val );
static computer_failure_type computer_failure_type_from_legacy_enum( int val );

void computer::load_legacy_data( const std::string &data )
{
    options.clear();
    failures.clear();

    std::istringstream dump( data );
    dump.imbue( std::locale::classic() );

    dump >> name >> security >> mission_id;

    name = string_replace( name, "_", " " );

    // Pull in options
    int optsize;
    dump >> optsize;
    for( int n = 0; n < optsize; n++ ) {
        std::string tmpname;

        int tmpaction;
        int tmpsec;

        dump >> tmpname >> tmpaction >> tmpsec;
        // Legacy missile launch option that got removed before `computer_action` was
        // refactored to be saved and loaded as string ids. Do not change this number:
        // `computer_action` now has different underlying values from back then!
        if( tmpaction == 15 ) {
            continue;
        }
        add_option( string_replace( tmpname, "_", " " ), computer_action_from_legacy_enum( tmpaction ),
                    tmpsec );
    }

    // Pull in failures
    int failsize;
    dump >> failsize;
    for( int n = 0; n < failsize; n++ ) {
        int tmpfail;
        dump >> tmpfail;
        add_failure( computer_failure_type_from_legacy_enum( tmpfail ) );
    }

    std::string tmp_access_denied;
    dump >> tmp_access_denied;

    // For backwards compatibility, only set the access denied message if it
    // isn't empty. This is to avoid the message becoming blank when people
    // load old saves.
    if( !tmp_access_denied.empty() ) {
        access_denied = string_replace( tmp_access_denied, "_", " " );
    }
}

void computer::serialize( JsonOut &jout ) const
{
    jout.start_object();
    jout.member( "name", name );
    jout.member( "mission", mission_id );
    jout.member( "security", security );
    jout.member( "alerts", alerts );
    jout.member( "next_attempt", next_attempt );
    jout.member( "options", options );
    jout.member( "failures", failures );
    jout.member( "access_denied", access_denied );
    jout.member( "eocs", eocs );
    jout.member( "chat_topics", chat_topics );
    jout.member( "values", values );
    jout.member( "loc", loc );
    jout.end_object();
}

void computer::deserialize( const JsonValue &jv )
{
    if( jv.test_string() ) {
        load_legacy_data( jv.get_string() );
    } else {
        const JsonObject jo = jv.get_object();
        jo.read( "name", name );
        jo.read( "mission", mission_id );
        jo.read( "security", security );
        jo.read( "alerts", alerts );
        jo.read( "next_attempt", next_attempt );
        jo.read( "options", options );
        jo.read( "failures", failures );
        jo.read( "access_denied", access_denied );
        jo.read( "eocs", eocs );
        jo.read( "chat_topics", chat_topics );
        jo.read( "values", values );
        jo.read( "loc", loc );
    }
}

void computer::remove_option( computer_action const action )
{
    for( auto it = options.begin(); it != options.end(); ++it ) {
        if( it->action == action ) {
            options.erase( it );
            break;
        }
    }
}

static computer_action computer_action_from_legacy_enum( const int val )
{
    switch( val ) {
        // Used to migrate old saves. Do not change the numbers!
        // *INDENT-OFF*
        default:
        case 0: return COMPACT_NULL;
        case 1: return COMPACT_OPEN;
        case 2: return COMPACT_LOCK;
        case 3: return COMPACT_UNLOCK;
        case 4: return COMPACT_TOLL;
        case 5: return COMPACT_SAMPLE;
        case 6: return COMPACT_RELEASE;
        case 7: return COMPACT_RELEASE_BIONICS;
        case 8: return COMPACT_TERMINATE;
        case 9: return COMPACT_PORTAL;
        case 10: return COMPACT_CASCADE;
        case 11: return COMPACT_RESEARCH;
        case 12: return COMPACT_MAPS;
        case 13: return COMPACT_MAP_SEWER;
        case 14: return COMPACT_MAP_SUBWAY;
        // options with action enum 15 are removed in load_legacy_data()
        case 16: return COMPACT_MISS_DISARM;
        case 17: return COMPACT_LIST_BIONICS;
        case 18: return COMPACT_ELEVATOR_ON;
        case 19: return COMPACT_AMIGARA_LOG;
        case 20: return COMPACT_AMIGARA_START;
        case 21: return COMPACT_COMPLETE_DISABLE_EXTERNAL_POWER;
        case 22: return COMPACT_REPEATER_MOD;
        case 23: return COMPACT_DOWNLOAD_SOFTWARE;
        case 24: return COMPACT_BLOOD_ANAL;
        case 25: return COMPACT_DATA_ANAL;
        case 26: return COMPACT_DISCONNECT;
        case 27: return COMPACT_EMERG_MESS;
        case 28: return COMPACT_EMERG_REF_CENTER;
        case 29: return COMPACT_TOWER_UNRESPONSIVE;
        case 30: return COMPACT_SR1_MESS;
        case 31: return COMPACT_SR2_MESS;
        case 32: return COMPACT_SR3_MESS;
        case 33: return COMPACT_SR4_MESS;
        case 34: return COMPACT_SRCF_1_MESS;
        case 35: return COMPACT_SRCF_2_MESS;
        case 36: return COMPACT_SRCF_3_MESS;
        case 37: return COMPACT_SRCF_SEAL_ORDER;
        case 38: return COMPACT_SRCF_SEAL;
        case 39: return COMPACT_SRCF_ELEVATOR;
        case 40: return COMPACT_OPEN_DISARM;
        case 41: return COMPACT_UNLOCK_DISARM;
        case 42: return COMPACT_RELEASE_DISARM;
        case 43: return COMPACT_IRRADIATOR;
        case 44: return COMPACT_GEIGER;
        case 45: return COMPACT_CONVEYOR;
        case 46: return COMPACT_SHUTTERS;
        case 47: return COMPACT_EXTRACT_RAD_SOURCE;
        case 48: return COMPACT_DEACTIVATE_SHOCK_VENT;
        case 49: return COMPACT_RADIO_ARCHIVE;
        // *INDENT-ON*
    }
}

static computer_failure_type computer_failure_type_from_legacy_enum( const int val )
{
    switch( val ) {
        // Used to migrate old saves. Do not change the numbers!
        // *INDENT-OFF*
        default:
        case 0: return COMPFAIL_NULL;
        case 1: return COMPFAIL_SHUTDOWN;
        case 2: return COMPFAIL_ALARM;
        case 3: return COMPFAIL_MANHACKS;
        case 4: return COMPFAIL_SECUBOTS;
        case 5: return COMPFAIL_DAMAGE;
        case 6: return COMPFAIL_PUMP_EXPLODE;
        case 7: return COMPFAIL_PUMP_LEAK;
        case 8: return COMPFAIL_AMIGARA;
        case 9: return COMPFAIL_DESTROY_BLOOD;
        case 10: return COMPFAIL_DESTROY_DATA;
        // *INDENT-ON*
    }
}

namespace io
{
template<>
std::string enum_to_string<computer_action>( const computer_action act )
{
    switch( act ) {
        // *INDENT-OFF*
        case COMPACT_NULL: return "null";
        case COMPACT_AMIGARA_LOG: return "amigara_log";
        case COMPACT_AMIGARA_START: return "amigara_start";
        case COMPACT_BLOOD_ANAL: return "blood_anal";
        case COMPACT_CASCADE: return "cascade";
        case COMPACT_COMPLETE_DISABLE_EXTERNAL_POWER: return "complete_disable_external_power";
        case COMPACT_CONVEYOR: return "conveyor";
        case COMPACT_DATA_ANAL: return "data_anal";
        case COMPACT_DEACTIVATE_SHOCK_VENT: return "deactivate_shock_vent";
        case COMPACT_DISCONNECT: return "disconnect";
        case COMPACT_DOWNLOAD_SOFTWARE: return "download_software";
        case COMPACT_ELEVATOR_ON: return "elevator_on";
        case COMPACT_EMERG_MESS: return "emerg_mess";
        case COMPACT_EMERG_REF_CENTER: return "emerg_ref_center";
        case COMPACT_EXTRACT_RAD_SOURCE: return "extract_rad_source";
        case COMPACT_GEIGER: return "geiger";
        case COMPACT_IRRADIATOR: return "irradiator";
        case COMPACT_LIST_BIONICS: return "list_bionics";
        case COMPACT_LOCK: return "lock";
        case COMPACT_MAP_SEWER: return "map_sewer";
        case COMPACT_MAP_SUBWAY: return "map_subway";
        case COMPACT_MAPS: return "maps";
        case COMPACT_MISS_DISARM: return "miss_disarm";
        case COMPACT_MISS_LAUNCH: return "miss_launch";
        case COMPACT_OPEN: return "open";
        case COMPACT_OPEN_GATE: return "open_gate";
        case COMPACT_CLOSE_GATE: return "close_gate";
        case COMPACT_OPEN_DISARM: return "open_disarm";
        case COMPACT_PORTAL: return "portal";
        case COMPACT_RADIO_ARCHIVE: return "radio_archive";
        case COMPACT_RELEASE: return "release";
        case COMPACT_RELEASE_BIONICS: return "release_bionics";
        case COMPACT_RELEASE_DISARM: return "release_disarm";
        case COMPACT_REPEATER_MOD: return "repeater_mod";
        case COMPACT_RESEARCH: return "research";
        case COMPACT_SAMPLE: return "sample";
        case COMPACT_SHUTTERS: return "shutters";
        case COMPACT_SR1_MESS: return "sr1_mess";
        case COMPACT_SR2_MESS: return "sr2_mess";
        case COMPACT_SR3_MESS: return "sr3_mess";
        case COMPACT_SR4_MESS: return "sr4_mess";
        case COMPACT_SRCF_1_MESS: return "srcf_1_mess";
        case COMPACT_SRCF_2_MESS: return "srcf_2_mess";
        case COMPACT_SRCF_3_MESS: return "srcf_3_mess";
        case COMPACT_SRCF_ELEVATOR: return "srcf_elevator";
        case COMPACT_SRCF_SEAL: return "srcf_seal";
        case COMPACT_SRCF_SEAL_ORDER: return "srcf_seal_order";
        case COMPACT_TERMINATE: return "terminate";
        case COMPACT_TOLL: return "toll";
        case COMPACT_TOWER_UNRESPONSIVE: return "tower_unresponsive";
        case COMPACT_UNLOCK: return "unlock";
        case COMPACT_UNLOCK_DISARM: return "unlock_disarm";
        // *INDENT-OFF*
        case NUM_COMPUTER_ACTIONS:
            break;
    }
    cata_fatal( "Invalid computer_action" );
}

template<>
std::string enum_to_string<computer_failure_type>( const computer_failure_type fail )
{
    switch( fail ){
        // *INDENT-OFF*
        case COMPFAIL_NULL: return "null";
        case COMPFAIL_ALARM: return "alarm";
        case COMPFAIL_AMIGARA: return "amigara";
        case COMPFAIL_DAMAGE: return "damage";
        case COMPFAIL_DESTROY_BLOOD: return "destroy_blood";
        case COMPFAIL_DESTROY_DATA: return "destroy_data";
        case COMPFAIL_MANHACKS: return "manhacks";
        case COMPFAIL_PUMP_EXPLODE: return "pump_explode";
        case COMPFAIL_PUMP_LEAK: return "pump_leak";
        case COMPFAIL_SECUBOTS: return "secubots";
        case COMPFAIL_SHUTDOWN: return "shutdown";
        // *INDENT-ON*
        case NUM_COMPUTER_FAILURES:
            break;
    }
    cata_fatal( "Invalid computer_failure_type" );
}
} // namespace io

template<>
struct enum_traits<computer_action> {
    static constexpr computer_action last = NUM_COMPUTER_ACTIONS;
};

template<>
struct enum_traits<computer_failure_type> {
    static constexpr computer_failure_type last = NUM_COMPUTER_FAILURES;
};

computer_option computer_option::from_json( const JsonObject &jo )
{
    translation name;
    jo.read( "name", name );
    const computer_action action = jo.get_enum_value<computer_action>( "action" );
    const int sec = jo.get_int( "security", 0 );
    return computer_option( name.translated(), action, sec );
}

computer_failure computer_failure::from_json( const JsonObject &jo )
{
    const computer_failure_type type = jo.get_enum_value<computer_failure_type>( "action" );
    return computer_failure( type );
}

std::unique_ptr<talker> get_talker_for( computer &me )
{
    return std::make_unique<talker_furniture>( &me );
}
std::unique_ptr<talker> get_talker_for( computer *me )
{
    return std::make_unique<talker_furniture>( me );
}
