// Copyright (c) 2015-2021 Georg Brandl.  Licensed under the Apache License,
// Version 2.0 <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at
// your option. This file may not be copied, modified, or distributed except
// according to those terms.

//! Constants for pickle opcodes.
//!
//! These constants use the names Python's pickle.py uses.  They are not in an
//! enum because it's not very useful to make it one.

use std::convert::TryFrom;

use crate::ErrorCode;

#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum Opcode {
    Mark = b'(',               // push special markobject on stack
    Stop = b'.',               // every pickle ends with STOP
    Pop = b'0',                // discard topmost stack item
    PopMark = b'1',            // discard stack top through topmost markobject
    Dup = b'2',                // duplicate top stack item
    Float = b'F',              // push float object; decimal string argument
    Int = b'I',                // push integer or bool; decimal string argument
    BinInt = b'J',             // push four-byte signed int
    BinInt1 = b'K',            // push 1-byte unsigned int
    Long = b'L',               // push long; decimal string argument
    BinInt2 = b'M',            // push 2-byte unsigned int
    None = b'N',               // push None
    String = b'S',             // push string; NL-terminated string argument
    BinString = b'T',          // push string; counted binary string argument
    ShortBinString = b'U',     // "     "   ;    "      "       "      " < 256 bytes
    Unicode = b'V',            // push Unicode string; raw-unicode-escaped'd argument
    BinUnicode = b'X',         // "     "       "  ; counted UTF-8 string argument
    Append = b'a',             // append stack top to list below it
    Dict = b'd',               // build a dict from stack items
    EmptyDict = b'}',          // push empty dict
    Appends = b'e',            // extend list on stack by topmost stack slice
    List = b'l',               // build list from topmost stack items
    EmptyList = b']',          // push empty list
    SetItem = b's',            // add key+value pair to dict
    Tuple = b't',              // build tuple from topmost stack items
    EmptyTuple = b')',         // push empty tuple
    SetItems = b'u',           // modify dict by adding topmost key+value pairs
    BinFloat = b'G',           // push float; arg is 8-byte float encoding
    Put = b'p',                // store stack top in memo; index is string arg
    BinPut = b'q',             // "     "    "   "   " ;   "    " 1-byte arg
    LongBinPut = b'r',         // "     "    "   "   " ;   "    " 4-byte arg
    Get = b'g',                // push item from memo on stack; index is string arg
    BinGet = b'h',             // "    "    "    "   "   "  ;   "    " 1-byte arg
    LongBinGet = b'j',         // push item from memo on stack; index is 4-byte arg
    Global = b'c',             // push self.find_class(modname, name); 2 string args
    StackGlobal = b'\x93',     // same as GLOBAL but using names on the stacks
    Reduce = b'R',             // apply callable to argtuple, both on stack
    Proto = b'\x80',           // identify pickle protocol
    Tuple1 = b'\x85',          // build 1-tuple from stack top
    Tuple2 = b'\x86',          // build 2-tuple from two topmost stack items
    Tuple3 = b'\x87',          // build 3-tuple from three topmost stack items
    NewTrue = b'\x88',         // push True
    NewFalse = b'\x89',        // push False
    Long1 = b'\x8a',           // push long from < 256 bytes
    Long4 = b'\x8b',           // push really big long
    BinBytes = b'B',           // push bytes; counted binary string argument
    ShortBinBytes = b'C',      // "     "   ;    "      "       "      " < 256 bytes
    ShortBinUnicode = b'\x8c', // push short string; UTF-8 length < 256 bytes
    BinUnicode8 = b'\x8d',     // push very long string
    BinBytes8 = b'\x8e',       // push very long bytes string
    EmptySet = b'\x8f',        // push empty set on the stack
    AddItems = b'\x90',        // modify set by adding topmost stack items
    FrozenSet = b'\x91',       // build frozenset from topmost stack items
    Memoize = b'\x94',         // store top of the stack in memo
    Frame = b'\x95',           // indicate the beginning of a new frame
    Inst = b'i',               // build & push class instance
    Obj = b'o',                // build & push class instance
    Build = b'b',              // call __setstate__ or __dict__.update()
    NewObj = b'\x81',          // build object by applying cls.__new__ to argtuple
    NewObjEx = b'\x92',        // like NEWOBJ but work with keyword only arguments
    ByteArray8 = b'\x96',      // push bytearray
}

impl Opcode {
    /// Get the byte value of this opcode
    #[inline]
    pub const fn to_u8(self) -> u8 {
        self as u8
    }
}

impl From<Opcode> for u8 {
    #[inline]
    fn from(opcode: Opcode) -> u8 {
        opcode as u8
    }
}

impl TryFrom<u8> for Opcode {
    type Error = ErrorCode;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            b'(' => Ok(Opcode::Mark),
            b'.' => Ok(Opcode::Stop),
            b'0' => Ok(Opcode::Pop),
            b'1' => Ok(Opcode::PopMark),
            b'2' => Ok(Opcode::Dup),
            b'F' => Ok(Opcode::Float),
            b'I' => Ok(Opcode::Int),
            b'J' => Ok(Opcode::BinInt),
            b'K' => Ok(Opcode::BinInt1),
            b'L' => Ok(Opcode::Long),
            b'M' => Ok(Opcode::BinInt2),
            b'N' => Ok(Opcode::None),
            b'S' => Ok(Opcode::String),
            b'T' => Ok(Opcode::BinString),
            b'U' => Ok(Opcode::ShortBinString),
            b'V' => Ok(Opcode::Unicode),
            b'X' => Ok(Opcode::BinUnicode),
            b'a' => Ok(Opcode::Append),
            b'd' => Ok(Opcode::Dict),
            b'}' => Ok(Opcode::EmptyDict),
            b'e' => Ok(Opcode::Appends),
            b'l' => Ok(Opcode::List),
            b']' => Ok(Opcode::EmptyList),
            b's' => Ok(Opcode::SetItem),
            b't' => Ok(Opcode::Tuple),
            b')' => Ok(Opcode::EmptyTuple),
            b'u' => Ok(Opcode::SetItems),
            b'G' => Ok(Opcode::BinFloat),
            b'p' => Ok(Opcode::Put),
            b'q' => Ok(Opcode::BinPut),
            b'r' => Ok(Opcode::LongBinPut),
            b'g' => Ok(Opcode::Get),
            b'h' => Ok(Opcode::BinGet),
            b'j' => Ok(Opcode::LongBinGet),
            b'c' => Ok(Opcode::Global),
            b'\x93' => Ok(Opcode::StackGlobal),
            b'R' => Ok(Opcode::Reduce),
            b'\x80' => Ok(Opcode::Proto),
            b'\x85' => Ok(Opcode::Tuple1),
            b'\x86' => Ok(Opcode::Tuple2),
            b'\x87' => Ok(Opcode::Tuple3),
            b'\x88' => Ok(Opcode::NewTrue),
            b'\x89' => Ok(Opcode::NewFalse),
            b'\x8a' => Ok(Opcode::Long1),
            b'\x8b' => Ok(Opcode::Long4),
            b'B' => Ok(Opcode::BinBytes),
            b'C' => Ok(Opcode::ShortBinBytes),
            b'\x8c' => Ok(Opcode::ShortBinUnicode),
            b'\x8d' => Ok(Opcode::BinUnicode8),
            b'\x8e' => Ok(Opcode::BinBytes8),
            b'\x8f' => Ok(Opcode::EmptySet),
            b'\x90' => Ok(Opcode::AddItems),
            b'\x91' => Ok(Opcode::FrozenSet),
            b'\x94' => Ok(Opcode::Memoize),
            b'\x95' => Ok(Opcode::Frame),
            b'i' => Ok(Opcode::Inst),
            b'o' => Ok(Opcode::Obj),
            b'b' => Ok(Opcode::Build),
            b'\x81' => Ok(Opcode::NewObj),
            b'\x92' => Ok(Opcode::NewObjEx),
            b'\x96' => Ok(Opcode::ByteArray8),
            _ => Err(ErrorCode::Unsupported(value as char)),
        }
    }
}

// Ops used for out-of-band buffers; these are unsupported.
// pub const NEXT_BUFFER      : u8 = b'\x97'; // push next out-of-band buffer
// pub const READONLY_BUFFER  : u8 = b'\x98'; // make top of stack readonly

// Ops only used for recursive objects; these are unsupported.
// pub const PERSID           : u8 = b'P';    // push persistent object; id is taken from string arg
// pub const BINPERSID        : u8 = b'Q';    //  "       "         "  ;  "  "   "     "  stack
// pub const EXT1             : u8 = b'\x82'; // push object from extension registry; 1-byte index
// pub const EXT2             : u8 = b'\x83'; // ditto, but 2-byte index
// pub const EXT4             : u8 = b'\x84'; // ditto, but 4-byte index
