use std::str::FromStr;
use serde::{Deserialize, Serialize, ser::SerializeMap};

#[derive(Debug, Serialize, Deserialize)]
pub enum SchoolType {
  #[serde(rename = "abjuration")]
  Abjuration,
  #[serde(rename = "conjuration")]
  Conjuration,
  #[serde(rename = "divination")]
  Divination,
  #[serde(rename = "enchantment")]
  Enchantment,
  #[serde(rename = "evocation")]
  Evocation,
  #[serde(rename = "illusion")]
  Illusion,
  #[serde(rename = "necromancy")]
  Necromancy,
  #[serde(rename = "transmutation")]
  Transmutation
}

impl SchoolType {
  pub fn to_string(&self) -> String {
    match self {
      SchoolType::Abjuration => "abjuration".to_string(),
      SchoolType::Conjuration => "conjuration".to_string(),
      SchoolType::Divination => "divination".to_string(),
      SchoolType::Enchantment => "enchantment".to_string(),
      SchoolType::Evocation => "evocation".to_string(),
      SchoolType::Illusion => "illusion".to_string(),
      SchoolType::Necromancy => "necromancy".to_string(),
      SchoolType::Transmutation => "transmutation".to_string()
    }
  }
}

impl FromStr for SchoolType {
  type Err = ();

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    match s {
      "abjuration" => Ok(SchoolType::Abjuration),
      "conjuration" => Ok(SchoolType::Conjuration),
      "divination" => Ok(SchoolType::Divination),
      "enchantment" => Ok(SchoolType::Enchantment),
      "evocation" => Ok(SchoolType::Evocation),
      "illusion" => Ok(SchoolType::Illusion),
      "necromancy" => Ok(SchoolType::Necromancy),
      "transmutation" => Ok(SchoolType::Transmutation),
      _ => Err(())
    }
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CastingTime {
  pub value: i32,
  #[serde(rename = "unit")]
  pub casting_type: String,
  pub note: Option<String>
}

#[derive(Debug, Serialize, Deserialize)]
pub enum SpellAttackType {
  #[serde(rename = "melee")]
  Melee,
  #[serde(rename = "ranged")]
  Ranged,
}

impl SpellAttackType {
  pub fn to_string(&self) -> String {
    match self {
      SpellAttackType::Melee => "melee".to_string(),
      SpellAttackType::Ranged => "ranged".to_string()
    }
  }
}

impl FromStr for SpellAttackType {
  type Err = ();

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    match s {
      "melee" => Ok(SpellAttackType::Melee),
      "ranged" => Ok(SpellAttackType::Ranged),
      _ => Err(())
    }
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub enum SpellDamageType {
  #[serde(rename = "acid")]
  Acid,
  #[serde(rename = "bludgeoning")]
  Bludgeoning,
  #[serde(rename = "cold")]
  Cold,
  #[serde(rename = "fire")]
  Fire,
  #[serde(rename = "force")]
  Force,
  #[serde(rename = "lightning")]
  Lightning,
  #[serde(rename = "necrotic")]
  Necrotic,
  #[serde(rename = "piercing")]
  Piercing,
  #[serde(rename = "poison")]
  Poison,
  #[serde(rename = "psychic")]
  Psychic,
  #[serde(rename = "radiant")]
  Radiant,
  #[serde(rename = "slashing")]
  Slashing,
  #[serde(rename = "thunder")]
  Thunder
}

impl SpellDamageType {
  pub fn to_string(&self) -> String {
    match self {
      SpellDamageType::Acid => "acid".to_string(),
      SpellDamageType::Bludgeoning => "bludgeoning".to_string(),
      SpellDamageType::Cold => "cold".to_string(),
      SpellDamageType::Fire => "fire".to_string(),
      SpellDamageType::Force => "force".to_string(),
      SpellDamageType::Lightning => "lightning".to_string(),
      SpellDamageType::Necrotic => "necrotic".to_string(),
      SpellDamageType::Piercing => "piercing".to_string(),
      SpellDamageType::Poison => "poison".to_string(),
      SpellDamageType::Psychic => "psychic".to_string(),
      SpellDamageType::Radiant => "radiant".to_string(),
      SpellDamageType::Slashing => "slashing".to_string(),
      SpellDamageType::Thunder => "thunder".to_string()
    }
  }
}

impl FromStr for SpellDamageType {
  type Err = ();

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    match s {
      "acid" => Ok(SpellDamageType::Acid),
      "bludgeoning" => Ok(SpellDamageType::Bludgeoning),
      "cold" => Ok(SpellDamageType::Cold),
      "fire" => Ok(SpellDamageType::Fire),
      "force" => Ok(SpellDamageType::Force),
      "lightning" => Ok(SpellDamageType::Lightning),
      "necrotic" => Ok(SpellDamageType::Necrotic),
      "piercing" => Ok(SpellDamageType::Piercing),
      "poison" => Ok(SpellDamageType::Poison),
      "psychic" => Ok(SpellDamageType::Psychic),
      "radiant" => Ok(SpellDamageType::Radiant),
      "slashing" => Ok(SpellDamageType::Slashing),
      "thunder" => Ok(SpellDamageType::Thunder),
      _ => Err(())
    }
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Range {
  #[serde(rename = "type")]
  pub range_type: String,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub value: Option<i32>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub unit: Option<String>
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Area {
  #[serde(rename = "type")]
  pub area_type: String,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub value: Option<i32>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub unit: Option<String>
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Duration {
  #[serde(rename = "type")]
  pub duration_type: DurationType,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub value: Option<i32>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub unit: Option<String>
}

#[derive(Debug, Serialize, Deserialize)]
pub enum DurationType {
  #[serde(rename = "concentration")]
  Concentration,
  #[serde(rename = "instantaneous")]
  Instantaneous,
  #[serde(rename = "timed")]
  Timed,
  #[serde(rename = "dispelled")]
  UntilDispelled,
  #[serde(rename = "special")]
  Special
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Source {
  pub source: String,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub page: Option<i32>
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Description {
  pub entries: Vec<Entry>
}

#[derive(Debug)]
pub struct Entry {
  pub text: Option<String>,
  pub list: Option<Vec<String>>,
  pub table: Option<EntryTable>
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EntryTable {
  pub headers: Vec<String>,
  pub rows: Vec<Vec<String>>
}

impl<'de> Deserialize<'de> for Entry {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
    let value = serde_json::Value::deserialize(deserializer)?;
    match value {
      serde_json::Value::String(s) => Ok(Entry {
        text: Some(s),
        list: None,
        table: None,
      }),
      serde_json::Value::Object(o) => {
        let text = match o.get("text") {
          Some(t) => match t.as_str() {
            Some(s) => Some(s.to_string()),
            None => return Err(serde::de::Error::custom("Invalid entry text"))
          },
          None => None
        };
        let list = match o.get("list") {
          Some(i) => match i.as_array() {
            Some(a) => {
              let mut list = Vec::new();
              for item in a {
                match item.as_str() {
                  Some(s) => list.push(s.to_string()),
                  None => return Err(serde::de::Error::custom("Invalid entry list item"))
                }
              }
              Some(list)
            },
            None => return Err(serde::de::Error::custom("Invalid entry list items"))
          },
          None => None
        };
        let table = match o.get("table") {
          Some(t) => match t.as_object() {
            Some(o) => {
              let mut headers = Vec::new();
              let mut rows = Vec::new();
              match o.get("headers") {
                Some(c) => match c.as_array() {
                  Some(a) => {
                    for item in a {
                      match item.as_str() {
                        Some(s) => headers.push(s.to_string()),
                        None => return Err(serde::de::Error::custom("Invalid entry table header"))
                      }
                    }
                  },
                  None => return Err(serde::de::Error::custom("Invalid entry table headers"))
                },
                None => return Err(serde::de::Error::custom("Missing entry table headers"))
              };
              match o.get("rows") {
                Some(r) => match r.as_array() {
                  Some(a) => {
                    for row in a {
                      match row.as_array() {
                        Some(a) => {
                          let mut row = Vec::new();
                          for item in a {
                            match item.as_str() {
                              Some(s) => row.push(s.to_string()),
                              None => return Err(serde::de::Error::custom("Invalid entry table row item"))
                            }
                          }
                          rows.push(row);
                        },
                        None => return Err(serde::de::Error::custom("Invalid entry table row"))
                      }
                    }
                  },
                  None => return Err(serde::de::Error::custom("Invalid entry table rows"))
                },
                None => return Err(serde::de::Error::custom("Missing entry table rows"))
              };
              Some(EntryTable {
                headers,
                rows
              })
            },
            None => return Err(serde::de::Error::custom("Invalid entry table"))
          },
          None => None
        };
        Ok(Entry {
          text,
          list,
          table
        })
      },
      _ => Err(serde::de::Error::custom("Invalid entry"))
    }
  }
}

impl Serialize for Entry {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
    let mut map = serializer.serialize_map(Some(1))?;
    if let Some(text) = &self.text {
      map.serialize_entry("text", text)?;
    }
    if let Some(list) = &self.list {
      map.serialize_entry("list", list)?;
    }
    if let Some(table) = &self.table {
      map.serialize_entry("table", table)?;
    }
    map.end()
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Components {
  pub verbal: bool,
  pub somatic: bool,
  pub material: bool,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub materials_needed: Option<String>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub materials_cost: Option<i32>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub materials_consumed: Option<bool>
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Effect {
  pub effect_type: Option<String>
}
